Pythonのメソッドのデフォルト引数に[]などを使うと、Pylintから次のように怒られました。
Dangerous default value [] as argument pylint(W0102)
とりあえず無視して適当に動かしたところ、案の定バグりました。コードの例を示します。
class MyClass:
def __init__(self, my_list=[]):
self.my_list = my_list
これをベースに議論を進めます。
何がまずいのか
まず、my_listをデフォルトのままMyClassインスタンスを生成します。すると、self.my_listにはデフォルト値である []
が入ります。その後、いろいろ処理をしているうちに、self.my_list
へappendしたりいろいろ変更を加えたりしたとしましょう。例えば、self.my_list.append('A')
という処理をしたとします。
次に、再びmy_list引数を与えずに別のMyClassインスタンスを生成したとき、このインスタンスのself.my_listには何が入るでしょうか。実は、['A']
が入ります。別のインスタンスでのself.my_listに対する変更が引き継がれているのです。
理由
これは、Pythonのlistがmutableであることに起因します。
1つ目のインスタンスのデフォルト値は []
ですが、Pythonのlistはmutableですので、その参照が入ります。つまり、self.my_list
とデフォルト値は同じ参照です。したがって、self.my_list
に関する操作はデフォルト値である []
にも影響を及ぼします。2つ目のインスタンスを生成するときのデフォルト値はself.my_listと同じ参照なので、前のインスタンスにおける変更が残存するのです。
対処法
-
copyを使う
import copy self.my_list = copy(my_list)
self.my_listとmy_listは入っている値は同じですが、その参照は異なります。つまり、デフォルト引数の
[]
とself.my_list
を別のものとして扱うわけです。 引数に正しくリストを与えた場合、このメソッドで行ったリストへの変更は外に出ると無効になります。 -
Noneにしておいて、中で変える
def __init__(self, my_list=None): if my_list is None: self.my_list = [] else: self.my_list = my_list
引数が与えられなかったとき(
None
)のみ、空のリスト[]
を生成し、self.my_listに代入します。この[]
は__init__
が実行されるたびに生成されるので複数のインスタンスで同じ参照になることはありません。 引数に正しくリストを与えた場合、このメソッドで行ったリストへの変更はメソッドの外でも反映されています。