このコードが「a、b、c、d」ではなく「d、d、d、d」と出力するのはなぜですか? 「a、b、c、d」を出力するように変更するにはどうすればよいですか?
cons = []
for i in ['a', 'b', 'c', 'd']:
cons.append(lambda: i)
print ', '.join([fn() for fn in cons])
奇妙なことに、これは変数のスコープの問題ではなく、python のfor
ループ (および python の変数) のセマンティクスの問題です。
ご想像のとおり、ラムダ内では、最も近い外側のスコープ内i
の変数が正しく参照されます。i
ここまでは順調ですね。
ただし、これは次のことが起こることを意味すると予想しています。
for each value in the list ['a', 'b', 'c', 'd']:
instantiate a new variable, i, pointing to the current list member
instantiate a new anonymous function, which returns i
append this function to cons
実際に何が起こるかは次のとおりです。
instantiate a new variable i
for each value in the list ['a', 'b', 'c', 'd']:
make i refer to the current list member
instantiate a new anonymous function, which returns i
append this function to cons
したがって、コードは同じ変数i
をリストに 4 回追加しています。ループが終了するまでi
に、値は'd'
.
Python 関数が引数の値を取得して値で返した場合、 の内容がへのi
呼び出しごとにappend
(または、さらに言えば、無名関数からの戻りごとに) コピーされるため、これに気付かないことに注意してください。で作成lambda
)。ただし、実際には、python 変数は常に特定のオブジェクトへの参照i
であるため、すべての 4 つのコピーが'd'
ループの終わりまでに参照されます。
クロージャを作成すると、ラムダ (この場合は ) によって「閉じられた」変数は、i
値ではなく名前でバインドされます。したがって、ラムダを呼び出すたびに、「i」の最後の値が使用されます。
これを機能させるために必要な簡単な修正は次のとおりです。
cons = []
for i in ['a', 'b', 'c', 'd']:
cons.append(lambda i=i: i)
print ', '.join([fn() for fn in cons])
理解としての「エリック」の答え:
cons =[lambda i= i:i for i in ['a', 'b', 'c', 'd']]
print ', '.join([fn() for fn in cons])