test.py
のようにリストに対してラムダ関数を反復しようとしていますが、関数オブジェクト自体ではなく、ラムダの呼び出し結果を取得したいです。しかし、次の出力は本当に混乱しました。
------test.py---------
#!/bin/env python
#coding: utf-8
a = [lambda: i for i in range(5)]
for i in a:
print i()
--------output---------
<function <lambda> at 0x7f489e542e60>
<function <lambda> at 0x7f489e542ed8>
<function <lambda> at 0x7f489e542f50>
<function <lambda> at 0x7f489e54a050>
<function <lambda> at 0x7f489e54a0c8>
呼び出し結果をt
に出力するときに変数名を次のように変更しましたが、すべてうまくいきます。それが一体何なのかと思っています。 ?
--------test.py(update)--------
a = [lambda: i for i in range(5)]
for t in a:
print t()
-----------output-------------
4
4
4
4
4
Python 2リスト内包表記では、変数が外部スコープに「リーク」します。
>>> [i for i in xrange(3)]
[0, 1, 2]
>>> i
2
Python 3では動作が異なることに注意してください:
>>> [i for i in range(3)]
[0, 1, 2]
>>> i
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'i' is not defined
ラムダを定義すると、2番目の例が示すように、現在の値ではなく、変数i
にバインドされます。新しい値をi
に割り当てると、ラムダは現在の値を返します:
>>> a = [lambda: i for i in range(5)]
>>> a[0]()
4
>>> i = 'foobar'
>>> a[0]()
'foobar'
ループ内のi
の値はラムダそのものなので、戻り値として取得します。
>>> i = a[0]
>>> i()
<function <lambda> at 0x01D689F0>
>>> i()()()()
<function <lambda> at 0x01D689F0>
[〜#〜] update [〜#〜]:Python 2.7の例:
Python 2.7.6 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a = [lambda: i for i in range(5)]
>>> for i in a:
... print i()
...
<function <lambda> at 0x7f1eae7f15f0>
<function <lambda> at 0x7f1eae7f1668>
<function <lambda> at 0x7f1eae7f16e0>
<function <lambda> at 0x7f1eae7f1758>
<function <lambda> at 0x7f1eae7f17d0>
Python 3.4と同じ:
Python 3.4.3 (default, Oct 14 2015, 20:28:29)
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = [lambda: i for i in range(5)]
>>> for i in a:
... print(i())
...
4
4
4
4
4
リスト内包表記による変数スコープに関する変更の詳細については、Guidoの 2010年のブログ投稿 を参照してください。
また、Python 3に別の変更を加えて、リストの内包表記とジェネレータ式の等価性を改善しました。 Python 2では、リストの内包表記により、ループ制御変数が周囲のスコープに「リーク」します。
x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'
ただし、Python 3では、ジェネレーター式と同じ実装戦略を使用して、リスト内包表記の「ダーティリトルシークレット」を修正することにしました。したがって、Python 3では、上記の例(print(x)を使用するように変更した後:-)は 'before'を出力し、リスト内包の 'x'が一時的にシャドウするが 'x'をオーバーライドしないことを証明します'周囲のスコープ内。
Pythonのクロージャーは late-binding です。つまり、リスト内の各ラムダ関数は、呼び出されたときに変数i
のみを評価し、not定義時にすべての関数が同じ値、つまりì
の最後の値(4)を返す理由です。
これを避けるための1つの方法は、i
の値をローカルの名前付きパラメーターにバインドすることです。
>>> a = [lambda i=i: i for i in range(5)]
>>> for t in a:
... print t()
...
0
1
2
3
4
もう1つのオプションは、 部分関数 を作成し、i
の現在の値をパラメーターとしてバインドすることです。
>>> from functools import partial
>>> a = [partial(lambda x: x, i) for i in range(5)]
>>> for t in a:
... print t()
...
0
1
2
3
4
編集:申し訳ありませんが、この種の質問は遅延バインディングに関することが多いため、最初は質問を誤読してください(ありがとう @ soon コメント)。
動作の2番目の理由は、他の人がすでに説明したように、Python2でリスト内包表記の変数が漏れていることです。 i
ループの反復変数としてfor
を使用する場合、各関数は(上記の理由により)単に[関数]であるi
の現在の値を出力します。別の名前(たとえば、t
)を使用する場合、関数はi
の最後の値をリスト内包ループ(4)に出力します。
lambda: i
は、iを返す引数のない匿名関数です。したがって、匿名関数のリストを生成し、後で(2番目の例で)名前t
にバインドし、()
で呼び出すことができます。非匿名関数でも同じことができることに注意してください。
>>> def f():
... return 42
...
>>> name = f
>>> name
<function f at 0x7fed4d70fb90>
>>> name()
42
@plamutは、質問の暗黙の他の部分に答えたばかりなので、私は答えません。