2015年11月16日

引数のデフォルト値は関数定義時のみ代入される!

Python の落とし穴として名高い(?)、引数のデフォルト値に関する注意について述べます。


Python は引数にデフォルト値を指定できますが、ここでの =演算子は関数定義時にのみ呼び出され、以降は呼ばれることがありません。
C言語をご存じの方なら static変数の挙動を思い浮かべれば分かりやすいと思います。
具体例を示します。

# デフォルト値が[]
>>> def func(a=[]):
...     a.append(1)
...     print(a)
...

# a=[] は関数定義時に一度だけ実行される。
# 以降 a は継続して使用される。
>>> func()
[1]
>>> func()
[1, 1]
>>> func()
[1, 1, 1]

この動作はかなり非直感的です。また、static変数的な動作は Python の思想とも合いません。

この問題は デフォルト値が mutable である時に起きやすいのですが、以下の例でも発生します。デフォルト値は関数定義時に確定するという点に注意して下さい。

>>> import time
>>> def print_time(t=time.time()):  # 関数定義時に t が確定
...     print(t)
...

>>> time.time()  # 現在時刻
1447445703.360414
>>> print_time()  # 定義時の時刻
1447445695.017109
>>> print_time()
1447445695.017109
>>> print_time()
1447445695.017109


この問題の対策としては、デフォルト値を None にしておき、引数が None であれば関数の冒頭で改めて値を代入する、という方法が知られています。
上で挙げた2つの関数に対策を施すと、それぞれ以下のようになります。
>>> def func(a=None):
...     # Noneなら新規空リスト
...     a = [] if a is None else a
...
...     a.append(1)
...     print(a)
...


>>> def print_time(t=None):
...     # Noneなら現在時刻
...     t = time.time() if t is None else t
...
...     print(t)
...


上記のような対策方法があるわけですが、これはいかにも回避策といった感じです。根本的には「引数のデフォルト値は関数定義時のみ代入される」という仕様自体がイマイチなように思います。

0 件のコメント:

コメントを投稿