Pythonのデフォルト引数にハマった。
よく考えれば当たり前だったはずなんですが、ハマってしまいました。
何かと言えば、デフォルト引数の評価です。
自戒のメモ。
デフォルト引数値の評価は一回
デフォルト引数は便利なんですが、一回しか評価されないので、
dictやlist等のミュータブル(変更可能)な値を使う場合は、
変更されることに注意しないといけない。
というのは一応常識のはずで、チュートリアルにもきちんと書いてあり、
自分もちゃんと読んだ記憶がある。
例えばこのfoo()の例。
チュートリアルにある例と本質は同じですが、
__init()__でも同じことがおこります。
# -*- coding:utf-8 -*- class foo(): L='' def __init__(self,l = []): l.append('a') self.L=l if __name__ == '__main__': f1=foo() print f1.L==['a'] #True f2=foo() print f2.L==['a','a'] #True f3=foo() print f3.L==['a','a','a'] #True print f1.L == f2.L == f3.L #True
で、今回ハマったのは、
# -*- coding:utf-8 -*- import time import random class bar(): t,r=0,0 def __init__(self, t=time.time(), r=random.getrandbits(64)): #params={ "t":t, "r":r } self.t=t self.r=r if __name__ == '__main__': b1=bar() #randomもtimeも最初しか評価されない。 b2=bar() bx=bar(10,20) #途中のこういうのも関係ない b3=bar() print b1.t == b2.t == b3.t #True print b1.r == b2.r == b3.r #True
評価が一回なので、何度呼んでも時間も乱数も更新されません。
t,rに対して値が固定されます。
こういった再現性の低い値が出るメソッドは、
メソッド内で呼ぶとテストしにくくなるので、
デフォルト引数にしてリストのアンパックと合わせて
値を設定すればいいと思ってやってしまいました。
# テストしにくい(ちょっと強引な)例 def fun1(self,a): t=time.time() r=random.getrandbits(64)): return a+t+r #テストできん。。 eq_(fun1(10),????)
あらかじめ渡したいデータをdictで持ってれば、
アンパックで一気にガッと渡してしまうことが出来るし、
かといって、引数は増えない。
# テストしやすい(ちょっと強引な)例 def fun2(self, a, t=int(time.time()), r=random.randint(0.1000)) return a+t+r param={ 'a':10, 't':1275931019, 'r':746 } #ロジックだけはテストできる eq_(fun2(**param),1275931775) #使う時は普通にaだけ渡せる fun2(10)
という感じで出来るかと思ったんですが、
先ほどのような場合はこれでは上手く行きませんね。
この場合は、一枚別のFacadeを挟んで、そちらでやるとかの方がいいかもしれません。
インタフェース設計し直しだなぁ。。
[追記]ソースがミスってたので修正。