最近、Python をいじり始めたのですが、クロージャの動作に奇妙な点があることに気づきました。次のコードを検討してください。
adders = [None, None, None, None]
for i in [0, 1, 2, 3]:
adders[i] = lambda a: i+a
print adders[1](3)
これは、単一の入力を受け取り、その入力に数値を加算して返す関数の単純な配列を構築します。関数は、for
反復子が から まで実行されるループ内i
に構築されます。これらの数値ごとに、それを取得して関数の入力に追加する関数が作成されます。最後の行は、 をパラメーターとして 2 番目の関数を呼び出します。驚いたことに、出力は でした。0
3
lambda
i
lambda
3
6
私は を予想していました4
。その理由は、Python ではすべてがオブジェクトであり、したがってすべての変数は本質的にそれへのポインタである、というものでした。lambda
のクロージャを作成するときにi
、 が現在指している整数オブジェクトへのポインタを格納すると予想していましたi
。つまり、i
新しい整数オブジェクトが割り当てられても、以前に作成されたクロージャには影響しないはずです。残念ながら、adders
デバッガ内で配列を検査すると、影響があることがわかります。すべてのlambda
関数は の最後の値、 を参照し、i
を返し3
ます。adders[1](3)
6
そこで私は次の点について疑問に思います。
- クロージャは正確には何を取得しますか?
- の値が変更されても影響を受けない方法で
lambda
の現在の値を取得するように関数を説得する最もエレガントな方法は何ですか?i
i
ループ(またはリストの内包表記、ジェネレータ式など)が使用されるケースに特化した、よりアクセスしやすく実用的な質問のバージョンについては、以下を参照してください。ループ(または内包)内で関数(またはラムダ)を作成するこの質問は、Python のコードにおける基本的な動作を理解することに重点を置いています。
Tkinterでボタンを作成する際の問題を解決しようとしてここに来た場合は、tkinter コマンド引数を渡す for ループでボタンを作成するより具体的なアドバイスについては。
見るobj.__closure__ には具体的に何が含まれているのでしょうか?Pythonがクロージャを実装する技術的な詳細については、早期バインディングと遅延バインディングの違いは何ですか?関連する用語の議論については。
ベストアンサー1
デフォルト値を持つ引数を使用して変数のキャプチャを強制することができます。
>>> for i in [0,1,2,3]:
... adders[i]=lambda a,i=i: i+a # note the dummy parameter with a default value
...
>>> print( adders[1](3) )
4
アイデアは、パラメータ(巧妙に と名付けられているi
)を宣言し、キャプチャしたい変数のデフォルト値( の値i
)を与えることです。