Cosnomi
Cosnomi

医療×IT / 医学生 / Web(React, Flask) / 機械学習(画像認識, Keras)

Twitter / GitHub / Keybase

Pythonのデフォルト引数で[]とか{}を使うべきではないという話

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.mylist にはデフォルト値である[]が入ります。その後、いろいろ処理をしているうちに self.mylist に append したりいろいろ変更を加えたりしたとしましょう。例えば、self.my_list.append('A')という処理をしたとします。

次に、再び mylist 引数を与えずに別の MyClass インスタンスを生成したとき、このインスタンスの self.mylist には何が入るでしょうか。実は、['A']が入ります。別のインスタンスでの self.my_list に対する変更が引き継がれているのです。

理由

これは、Python の list が mutable であることに起因します。

1 つ目のインスタンスのデフォルト値は[]ですが、Python の list は mutable ですので、その参照が入ります。つまり、self.my_list とデフォルト値は同じ参照です。したがって、self.my_list に関する操作はデフォルト値である[]にも影響を及ぼします。2 つ目のインスタンスを生成するときのデフォルト値は self.my_list と同じ参照なので、前のインスタンスにおける変更が残存するのです。

対処法

  1. copy を使う

    import copy
    self.my_list = copy(my_list)

    self.mylist と mylist は入っている値は同じですが、その参照は異なります。つまり、デフォルト引数の[]self.my_listを別のものとして扱うわけです。 引数に正しくリストを与えた場合、このメソッドで行ったリストへの変更は外に出ると無効になります。

  2. 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__が実行されるたびに生成されるので複数のインスタンスで同じ参照になることはありません。 引数に正しくリストを与えた場合、このメソッドで行ったリストへの変更はメソッドの外でも反映されています。