きり丸の技術日記

技術検証したり、資格等をここに残していきます。

Pythonのデフォルト引数では配列ではなくNoneを使わないとダメ(ミュータブルはNG)

有名な話ですがハマったのでブログにします。

環境

  • Python
    • 3.9

事象

Pythonでデフォルト引数に配列を定義すると、意図せずに同じインスタンスを使用してしまう。

def target_1(value, list=[]):
    list.append(value)
    return list

def test_01():
    a = target_1("1")
    # ["1"]
    assert len(a) == 1
    b = target_1("2")
    # a, b 両方とも次の値になる
    # ["1", "2"]
    assert len(a) == 2
    assert len(b) == 2
    # インスタンスが同じであることがわかる
    assert a is b

原因と対策

公式のヘルプに原因と対策が載っていました。

デフォルト引数値は関数定義が実行されるときに左から右へ評価されます。 これは、デフォルト引数の式は関数が定義されるときにただ一度だけ評価され、同じ "計算済みの" 値が呼び出しのたびに使用されることを意味します。この仕様を理解しておくことは特に、デフォルト引数値がリストや辞書のようなミュータブルなオブジェクトであるときに重要です: 関数がこのオブジェクトを変更 (例えばリストに要素を追加) すると、このデフォルト引数値が変更の影響を受けてしまします。一般には、これは意図しない動作です。このような動作を避けるには、デフォルト値として None を使い、この値を関数本体の中で明示的にテストします。例えば以下のようにします:

def whats_on_the_telly(penguin=None):
  if penguin is None:
    penguin = []
  penguin.append("property of the zoo")
  return penguin

終わりに

正直、誰も喜ばない仕様だと思うんですけどね…。変数だった場合はインスタンスを共有するのは分かるのですが、[]でも共有してしまう仕様だとは知りませんでした。試したことはありませんが、静的解析で検知できればいいですね。

参考情報