6

理解に苦労しているPythonの動作に出くわしました。これは概念実証コードです。

from functools import partial

if __name__ == '__main__':
    sequence = ['foo', 'bar', 'spam']
    loop_one = lambda seq: [lambda: el for el in seq]
    no_op = lambda x: x
    loop_two = lambda seq: [partial(no_op, el) for el in seq]
    for func in (loop_one, loop_two):
        print [f() for f in func(sequence)]

上記の出力は次のとおりです。

['spam', 'spam', 'spam']
['foo', 'bar', 'spam']

の動作loop_oneは驚くべきものですloop_two。 :elは各ループで変化する不変の値 (文字列) ですが、ループがリサイクルする場合のようlambdaに、「ループ変数」へのポインターを格納しているようです。シーケンスの各要素に対して同じメモリ アドレス。

上記の動作は、 for ループを含む本格的な関数と同じです (したがって、リスト内包表記ではありません)。

しかし、待ってください: まだまだあります...もっと不可解です!

次のスクリプトは次のように機能しますloop_one

b = []
for foo in ("foo", "bar"):
    b.append(lambda: foo)

print [a() for a in b]

(出力: ['bar', 'bar'])

fooしかし、変数名を に置き換えるとどうなるか見てみましょうa:

b = []
for a in ("foo", "bar"):
    b.append(lambda: a)

print [a() for a in b]

(出力: [<function <lambda> at 0x25cce60>, <function <lambda> at 0x25cced8>])

ここで何が起こっているのか分かりますか? インタープリターの基になる C 実装に関連する落とし穴があるに違いないと思いますが、この動作が異なる実装間で一貫しているかどうかをテストするもの (Jthon、PyPy など) は他にありません。

4

2 に答える 2

4

変数 (foo次の例) は、ラムダの作成時ではなく、ラムダの呼び出し時にバインドされます。

>>> b = []
>>> for foo in ("foo", "bar"):
...     b.append(lambda: foo)
...
>>> foo = "spam"
>>> print [a() for a in b]
['spam', 'spam']

>>> b = []
>>> for foo in ("foo", "bar"):
...     b.append(lambda foo=foo: foo)
...
>>> print [a() for a in b]
['foo', 'bar']
于 2013-11-04T13:18:02.267 に答える
4

lambda: elで使用されている関数は、ローカル スコープで定義されていないloop_one変数を参照しています。elしたがって、Python は、 other の外側のスコープで次にそれを探しますlambda

lambda seq: [lambda: el for el in seq]

いわゆるLEGBルールに従って。

が呼び出されるまでlambda: elに、この囲んでいるラムダは (もちろん) 既に呼び出されており、リスト内包表記は評価されています。リスト内包表記で使用される は、このel囲みラムダのローカル変数です。elその値は、Python がinの値を探すときに返される値ですlambda: el。その値 forelは、リスト内包表記内のすべての異なる関数で同じです: これは、ループ内でlambda: el割り当てられる最後の値です。したがって、は常にの最後の値です。elfor el in seqel'spam'seq


loop_two. _ もう 1 つの方法はel、デフォルト値を持つローカル変数として定義することです。

loop_one = lambda seq: [lambda el=el: el for el in seq]
于 2013-11-04T13:20:00.463 に答える