デフォルト引数の評価タイミング

Twitterより転載

shimizukawa: あるあるw あわせて覚えたい:関数引数のデフォルト値に arg=[] したときの挙動 RT @nishio: @atsuoishimoto 正しいけど不親切だと思ったので補足しました: 同じ値がN個入っているリストの作り方 http://bit.ly/eW2oaF

はいはい、じゃあそれも書いておきましょう!

Pythonの長所の一つはdisでバイトコードが読めることだと思うので疑問に思ったらとりあえずみんなdisればいいとおもうよ!

>>> import dis
>>> def bar():
...     def foo(x=1, y=2, z=[]):
...         pass
... 
>>> dis.dis(bar)
  2           0 LOAD_CONST               1 (1)
              3 LOAD_CONST               2 (2)
              6 BUILD_LIST               0
              9 LOAD_CONST               3 (<code object foo at 0x10046ccd8, file "<stdin>", line 2>)
             12 MAKE_FUNCTION            3
             15 STORE_FAST               0 (foo)
             18 LOAD_CONST               0 (None)
             21 RETURN_VALUE        

不慣れな人のためにわかり易く説明を付けると:

LOAD_CONST (1) # 1を用意して
LOAD_CONST (2) # 2を用意して
BUILD_LIST # リストを作って
LOAD_CONST (<code object>) # 関数の中身を用意して
MAKE_FUNCTION 3 # 3つの値をデフォルト引数につかって関数を作成
STORE_FAST (foo) # その関数にfooって名前をつけて
LOAD_CONST (None) # Noneを用意して
RETURN_VALUE # それを返す

関数を定義する前にデフォルト値[]を作るBUILD_LISTが走っているのがわかる。っていうことはどういうことかというと、当然そのデフォルト値の実体は関数の実体と一対一なので下のような挙動が起きる。

>>> def foo(x=[]):
...     x.append(1)
...     print x
... 
>>> foo()
[1]
>>> foo()
[1, 1]

実用的な例で言えば、「引数で時刻を指定する関数があって、その引数を省略したら現在の時刻を使いたい」なんてケース。

>>> from datetime import datetime
>>> def foo(when=datetime.now()):
...     print when
... 
>>> foo()
2011-02-07 20:42:43.578936 # 引数省略してもちゃんと動いた
>>> foo(datetime.now())
2011-02-07 20:42:51.446415 # 引数指定してもちゃんと動いた。
# よーしうまく動いているぞ、なんて思ったら…
>>> foo()
2011-02-07 20:42:43.578936 # 引数省略したときには同じ時刻が返ってくる…

こういう場合の対処法は引数の省略時の値をNoneにしておいて「Noneの時には現在時刻」と書くことかと思う。

>>> def foo(when=None):
...     if not when: when = datetime.now()
...     print when
... 
>>> foo()
2011-02-07 20:43:36.233154
>>> foo()
2011-02-07 20:43:37.539578 # ちゃんと動いている