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在以下示例中)不是在创建 lambda 时绑定,而是在调用 lambda 时绑定。

>>> 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调用的时候,这个封闭的 lambda 已经(当然)已经被调用并且列表理解已经被评估。列表推导中el使用的 是此封闭 lambda 中的局部变量。它的值是 Python 查找 in 的值时返回的ellambda: el。对于列表推导中的所有不同函数,该值el相同lambda: el的:它是分配给循环el中的最后一个值。for el in seq因此,el始终是'spam'中的最后一个值seq


您已经找到了一种解决方法,即使用闭包,例如您的loop_two. 另一种方法是定义el为具有默认值的局部变量:

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