web-dev-qa-db-ja.com

リスト内包表記のラムダは、呼び出されるとラムダを返します

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
53
keyuan7569

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'をオーバーライドしないことを証明します'周囲のスコープ内。

47
niemmi

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)に出力します。

19
plamut

lambda: iは、iを返す引数のない匿名関数です。したがって、匿名関数のリストを生成し、後で(2番目の例で)名前tにバインドし、()で呼び出すことができます。非匿名関数でも同じことができることに注意してください。

>>> def f():
...   return 42
... 
>>> name = f
>>> name
<function f at 0x7fed4d70fb90>
>>> name()
42

@plamutは、質問の暗黙の他の部分に答えたばかりなので、私は答えません。

4
nigel222