dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

[python]関数でデフォルト引数を使う時の注意点

「パーフェクトpython」読んでます。

1個1個のトピックについて非常に説明が詳しいので、python という言語を勉強するのに役立ちます。

今回は「5章 関数」を読んでいて「これは知らなかった!」ということがあったのでメモ。

pythonで関数を定義する時、引数にデフォルト引数を設定することができますよね。

def hoge(foo, bar, baz='xxx'):
    print '%s:%s:%s' % (foo, bar, baz)

この関数を呼び出す時に引数bazを省略すると、デフォルト引数として設定した'xxx'が使われます。

>>> hoge('aaa', 'bbb', 'ccc')
aaa:bbb:ccc
>>> hoge('aaa', 'bbb')
aaa:bbb:xxx

で、注意するのはこういった例。

>>> def append_number(items=[]):
...     items.append(1)
...     return items
...
>>> append_number()
[1]
>>> append_number()
[1, 1]
>>> append_number()
[1, 1, 1]

上記のように、デフォルト引数に対して関数内で破壊的な処理をする と、
関数を呼び出すたびに処理の影響が蓄積されてしまいます。

デフォルト引数が作られるタイミングは「関数が呼び出される時」ではなく
「関数が作成された時」だから、ということのようですね。
このへんきちんと理解してコードを書かないと、思わぬ挙動をしてしまいそうです。

このことはけっこうネットでも取り上げられており、「引数のデフォルト値はImmutableなものにすること」などと書いてました。

書籍では、↑の関数を以下のように修正してましたが

>>> omit = object()
>>> def append_number(items=omit):
...     if items is omit:
...         items = []
...     items.append(1)
...     return items
...
>>> append_number()
[1]
>>> append_number()
[1]

個人的には紹介したQiitaの記事のように、Noneを指定する方がしっくりきました。
クラスのメソッドとして定義するときにメソッドの外側に omit = object() とか書きたくないので。

>>> def append_number(items=None):
...     if items is None:
...         items = []
...     items.append(1)
...     return items
...
>>> append_number()
[1]
>>> append_number()
[1]