PyQtでウィジットを作成するときなど、関数やメソッドをコールバックとして利用するために気軽にlambda
を作成してパラメータを減らしたものをコールバックとしてセットするというようなことをすることがあります。
この場合、次の例のように、コールバックを生成するメソッド内の一時的なローカル変数を
コールバックの中で参照すると、その変数は自由変数となり、意図しない結果となる場合があるので注意が必要です。
class Parent:
def __init__(self):
self.children = {"foo": Child(), "bar": Child()}
for name, child in self.children.items():
child.get_name = lambda: self.get_child_name(name)
def get_child_name(self, name):
return name
class Child:
pass
p.children["foo"].get_name()
は、"foo"
を返すことを期待しますが、実際は"bar"
を返します。
>>> p = Parent()
>>> p.children["foo"].get_name()
'bar'
__init__
の中で、name
はforループで使用する一時的なローカル変数として使っていますが、2つのChild
オブジェクトのget_name
属性に代入されるlambda
の中で使用されているため、自由変数になります。
forループの中で最初のChild
オブジェクトにlambda
をセットするときはname
には"foo"
がセットされていますが、その後もそのlambda
は、同じname
変数を参照し続けるため、name
にはその後"bar"
がセットされて意図しない動作となるわけです。下記のとおり、p.children["foo"].get_name()
とp.children["bar"].get_name()
が同じ変数を参照していることが分かります。
>>> p.children["foo"].get_name() is p.children["bar"].get_name()
True