Pythonで関数のデフォルトの引数にオブジェクトを指定すると同じオブジェクトを参照してしまう問題

普段からPythonでWebアプリケーションを開発する仕事をしていますが、コーディングしているときについついやりがちなミスなどもあります。
今回はそれの一例として、デフォルト引数のオブジェクトを別でも参照してしまう問題について取り上げます。

関数を定義する際などに引数に何も入っていなかった場合は、デフォルト引数の値が使われます。例えば以下のようなコードです。

def test(a=[]):
    return a

ここで test() の関数を呼びだす時に、引数を何も指定しないとデフォルト引数の a=[] が使われます。

何も引数を指定しなかった場合は空のリスト戻り値として返ってきます。

>>> a = test()
>>> a
[]

ここで組込み関数である id() を使用して 関数test() が返すオブジェクトの識別値を確認できます。
id() からは何かしらの整数が返ってきます。

>>> a = test()
>>> id(a)

ここでtest関数が返す値をaとbの変数に格納して、それぞれをid()関数で比較してみます。
するとTrueと返ってくるように両者のオブジェクトが一致していることから、同じオブジェクトが参照されてしまっています。

>>> a = test()
>>> a
[]
>>> b = test()
>>> b
[]
>>> id(a) == id(b)
True

ここでクラスのコンストラクターでやるとこれの何が問題かわかりやすくなります。
Exampleというクラスを作ってみてコンストラクターのデフォルト引数にa=[]という値を指定してみます。

>>> class Example:
...   def __init__(self, a=[]):
...     self.value = a
...
>>> a = Example()
>>> a.value
[]

上記の関数同様にExampleクラスのインスタンスを生成してインスタンスをaとbという変数に格納してみます。
ここでもid()で比較してみるとオブジェクトが一致します。

>>> a = Example()
>>> a.value
[]
>>> b = Example()
>>> b.value
[]
>>> id(a.value) == id(b.value)
True

ここでaのインスタンス変数であるリストのvalueappend()で値を加えてみます。
すると以下のような現象が起きてしまい、bのインスタンス変数であるはずのvalueも書き換えられてしまいます。

>>> a = Example()
>>> a.value
[]
>>> b = Example()
>>> b.value
[]
>>> a.value.append(10)
>>> a.value
[10]
# 10という値がリストの中に格納されている

>>> b.value
[10]

# なぜかaのインスタンス変数であるvalueに値を格納したはずなのに、bも同じリストを参照してしまっている。

このような現象は引数にオブジェクトを渡すと同じオブジェクトを参照されてしまいます。
上記のExampleクラスにしろ、test関数にしろデフォルト引数として[ ]という空のリストのオブジェクトを渡してしまっているのが原因です。


それでもデフォルト引数に何も入っていなかった場合に空のリストを引数として入れたい場合はどうすれば良いでしょうか。
自分の場合は主に以下のようなやり方しています。

>>> class Example:
...   def __init__(self, a=None):
...     self.value = a
...     if a is None:
...         self.value = list()
...
>>> a = Example()
>>> b = Example()
>>> a.value.append(10)
>>> a.value
10
>>> b.value
[]


デフォルト引数aにはNoneを指定しておき、aがNoneならインスタンス変数valueにはlist()を与えるようにしています。
こうすることでaインスタンスのvalueにappendで値を加えても、bのvalueには何も影響を与えません。


この記事が気に入ったらサポートをしてみませんか?