Cosnomi

プログラミングをする医学生。物を作ること、自動化すること、今まで知らなかったことを知ることが好き。TypeScript書いたり、Pythonで機械学習したりなど。

Twitter / GitHub / Keybase
TOP >

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


最後まで読んでいただきありがとうございます。コメントフォームは設置していません。訂正・意見・感想などはTwitter(@cosnomi)などへお願いします。
記事一覧へ