外部変数のキャプチャ

この記事は Python Tips Advent Calendar 2012 6日目の記事です。


Python では、ラムダ式やローカルでの def 関数は、それらを定義した時に、外にあるローカル変数の参照を、ラムダ式や関数のオブジェクトの内部に保存します。

>>> x = 10
>>> f = lambda: x     # x をラムダ式オブジェクトに保存する
>>> f()     # f は x への参照を保持している
10

ローカル変数のコピーではなく "参照" であるという点に注意です。

>>> x = 10
>>> idx = id(x)     # ここで使っている x と
>>> f = lambda: idx == id(x)     # ここで使っている x は同じオブジェクトなので、同じ ID になる
>>> f()
True

これは便利なこともあるのですが、例えば以下の様な挙動になることもあるので注意です。

>>> xs = [1,2,3]
>>> fs = [(lambda: x) for x in xs]
>>> for f in fs:
...     f()
3
3
3

普通に見ると 1 2 3 と出力されそうですが、for x in xs で作った x は、ループ中は同じオブジェクト(ループ中常に id(x) が同じ)になるので、全てのラムダ式で同じオブジェクトを参照することになり、最後の値である 3 が出力されることになります。


これを解決するには、例えば以下のようにラムダ式で引数を取るようにして、partial でその引数をバインドしてやるという手が考えられます。

>>> from functools import partial
>>> fs = [partial(lambda x: x, x) for x in xs]
>>> for f in fs:
...     f()
1
2
3


あるいは、引数のデフォルト値は定義時にバインドされるという特性を利用して、

>>> fs = [(lambda x=x: x) for x in xs]
>>> for f in fs:
...     f()
1
2
3

のように書くこともできます。


.