5月 07 2009
Python のデフォルト引数は一度しか評価されない
最近は仕事なんかで Python でコードを書くことが結構あるのだけど、知らずに惑ったことの一つに、関数のデフォルト引数が定義時にしか評価されない仕様というのがある。
デフォルトパラメタ値は関数定義を実行する際に値評価されます。 これは、デフォルトパラメタの式は関数を定義するときにただ一度だけ評価され、同じ”計算済みの”値が全ての呼び出しで使われることを意味します。
例えば無理矢理な例だけど、こんな風に使うことも可能。関数ローカルの変数扱いですな。
def fib(num, cache=[0,1]):
assert len(cache) > 1
if len(cache) > num:
return cache[num]
else:
cache[num:num] = [fib(num-1) + fib(num-2)]
return cache[num]
print fib(10) # => 55
試しに、この仕様に反して、 Python でも呼びだしの度にデフォルト引数が初期化されるようなコードを、デコレータを使って書いてみた。 デコレータというのは簡単に言うと関数に被せるラッパーを書くためのシンタックスシュガー。
def newdefaults(fn):
import copy
arity = fn.func_code.co_argcount
newdefaults.defaults = copy.deepcopy(fn.func_defaults)
def newfn(*args):
args = list(args)
while len(args) < arity:
index = len(args) - arity + len(newdefaults.defaults)
if index < 0: # 引数たりない
return fn(*args)
arg = newdefaults.defaults[index]
args.append(copy.deepcopy(arg))
return fn(*args)
return newfn
if __name__ == '__main__':
@newdefaults
def f(v1, v2, d=0, a=[0]):
print "f(%s, %s, %s, %s)" % (v1, v2, d, a)
a.append(v1)
return a
assert f(1,0) == [0,1]
assert f(2,0) == [0,2]
try:
f()
assert False
except TypeError, e:
print e # OK
except:
print "Unexpected error"
assert False
結果はこう。
f(1, 0, 0, [0])
f(2, 0, 0, [0])
f() takes at least 2 arguments (0 given)
*argsおよび**kwargsを引数にとる関数には適用できないはず。- ポイント: 関数の様子はある程度は function のプロパティ、
f.func_defaultsやf.func_codeなどから分かる。もっと詳しく調べたいときはimport inspect。 - deepcopy しちゃうとまずい時もありそうだけどまあいいか。
Tags: python
