本文介绍了循环中的奇怪lambda行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我偶然发现了我很难理解的python行为.这是概念验证代码:

I stumbled upon a behaviour in python that I have a hard time understanding. This is the proof-of-concept code:

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似乎存储指向循环变量"的指针,就像循环将为序列中的每个元素循环使用相同的内存地址一样.

The behaviour of loop_one is surprising to me as I would expect it to behave as loop_two:el is an immutable value (a string) that changes at each loop, but lambda seems to store a pointer to the "looping variable", like if the loop would recycle the same memory address for each element of the sequence.

上面的行为与带有for循环的成熟函数相同(因此它不是列表理解语法).

The above behaviour is the same with full-blown functions with a for loop in them (so it is not a list-comprehension syntax).

但请稍等:还有更多...以及更多令人困惑的地方!

以下脚本的工作方式类似于loop_one:

The following script works like loop_one:

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

print [a() for a in b]

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

但是请注意将变量名称foo替换为a会发生什么:

But watch what happens when one substitute the variable name foo with 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或类似的东西)来测试这种行为在不同实现之间是否一致.

Any idea of what is happening here? I suspect there must be some gotcha related to the underlying C implementation of my interpreter, but I haven't anything else (Jthon, PyPy or similar) to test if this behaviour is consistent across different implementations.

推荐答案

loop_one中使用的函数lambda: el引用在本地范围中未定义的变量el.因此,Python会在其他lambda的封闭范围内寻找它:

The function lambda: el used in loop_one refers to a variable el which is not defined in the local scope. Therefore, Python looks for it next in the enclosing scope of the other lambda:

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

按照所谓的 LEGB规则.

在调用lambda: el时,此包围的lambda(当然)已被调用,并且列表理解已得到评估.列表推导中使用的el是此封闭lambda中的局部变量.它的值是当Python在lambda: el中查找el的值时返回的值.对于列表理解中所有不同的lambda: el函数,el的值都是相同:这是在for el in seq循环中分配给el的最后一个值.因此,el始终是'spam',是seq中的最后一个值.

By the time lambda: el is called, this enclosing lambda has (of course) already been called and the list comprehension has been evaluated. The el used in the list comprehension is a local variable in this enclosing lambda. Its value is the one returned when Python looks for the value of el in lambda: el. That value for el is the same for all the different lambda: el functions in the list comprehension: it is the last value assigned to el in the for el in seq loop. Thus, el is always 'spam', the last value in seq.

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

You've already found one workaround, to use a closure such as your loop_two. Another way is to define el as a local variable with a default value:

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

这篇关于循环中的奇怪lambda行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-29 16:26