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
関連記事