52

我想从 Python 中的常量列表创建一个 lambda 对象列表;例如:

listOfNumbers = [1,2,3,4,5]
square = lambda x: x * x
listOfLambdas = [lambda: square(i) for i in listOfNumbers]

但是,当我运行它们时,这将创建一个 lambda 对象列表:

for f in listOfLambdas:
    print f(),

我希望它会打印

1 4 9 16 25

相反,它打印:

25 25 25 25 25

似乎所有 lambdas 都被赋予了错误的参数。我做错了什么,有没有办法解决它?我认为我在 Python 2.4 中。

编辑:多一点尝试的东西等等想出了这个:

listOfLambdas = []
for num in listOfNumbers:
    action = lambda: square(num)
    listOfLambdas.append(action)
    print action()

打印从 1 到 25 的期望方格,然后使用前面的 print 语句:

for f in listOfLambdas:
    print f(),

仍然给我所有25的s。这两个打印调用之间现有的 lambda 对象是如何变化的?

相关问题:为什么 map() 和列表理解的结果不同?

4

9 回答 9

39

你有:

listOfLambdas = [lambda: i*i for i in range(6)]

for f in listOfLambdas:
    print f()

输出:

25
25
25
25
25
25

你需要咖喱!除了好吃之外,使用这个默认值“hack”。

listOfLambdas = [lambda i=i: i*i for i in range(6)]

for f in listOfLambdas:
    print f()

输出:

0
1
4
9
16
25

注意i=i. 这就是魔法发生的地方。

于 2009-01-17T01:51:59.027 回答
34

我猜您在列表推导中创建的 lambda 绑定到变量 i 最终以 5 结束。因此,当您在事后评估 lambda 时,它们都绑定到 5 并最终计算25. 在你的第二个例子中,同样的事情发生在 num 上。当您在循环内评估 lambda 时,它的 num 没有改变,因此您得到了正确的值。循环后,num 为 5...

我不太确定您要做什么,所以我不确定如何提出解决方案。这个怎么样?

def square(x): return lambda : x*x
listOfLambdas = [square(i) for i in [1,2,3,4,5]]
for f in listOfLambdas: print f()

这给了我预期的输出:

1
4
9
16
25

另一种思考方式是 lambda 在创建时“捕获”其词法环境。所以,如果你给它num它实际上并没有解析该值,直到它被调用。这既令人困惑又强大。

于 2009-01-17T01:37:51.473 回答
9

当函数语句被执行时,它们被绑定到它们的(词法上的)封闭范围。

在您的代码段中,lambda 绑定到全局范围,因为for套件不是作为 Python 中的独立范围单元执行的。在for循环结束时,num被绑定在封闭范围内。演示:

for num in range(1, 6):
    pass
assert num == 5 # num is now bound in the enclosing scope

for因此,当您在循环中绑定标识符时,您实际上是在操纵封闭范围。

for num in range(1, 6):
    spam = 12
assert num == 5 # num is now bound in the enclosing scope
assert spam == 12 # spam is also bound in the enclosing scope

列表推导的相同处理:

[num for num in range(1, 6)]
assert num == 5

令人兴奋,我知道。任何人,根据我们新发现的知识,我们可以确定您正在创建的 lambda 是指num绑定在封闭范围内的(单个)标识符。这应该更有意义:

functions = []
for number in range(1, 6):
    def fun():
        return number
    functions.append(fun)
assert all(fun() == 5 for fun in functions)
assert all(fun() is number for fun in functions)

这是最酷的部分,它更能证明这一点:

# Same as above -- commented out for emphasis.
#functions = []
#for number in range(1, 6):
#    def fun():
#        return number
#    functions.append(fun)
#assert all(fun() == 5 for fun in functions)
#assert all(fun() is number for fun in functions)
number = 6 # Rebind 6 in the scope and see how it affects the results.
assert all(fun() == 6 for fun in functions) 

因此,解决方案当然是为每个number要绑定的对象创建一个新的封闭范围。在 Python 中,您可以使用模块、类和函数创建新的封闭范围。使用一个函数来为另一个函数创建新的封闭范围是很常见的。

在 Python 中,闭包是一个返回另一个函数的函数。有点像函数构造函数。请查看get_fun以下示例:

def get_fun(value):
    """:return: A function that returns :param:`value`."""
    def fun(): # Bound to get_fun's scope
        return value
    return fun

functions = []
for number in range(1, 6):
    functions.append(get_fun(number))
assert [fun() for fun in functions] == range(1, 6)

既然get_fun是一个函数,它就有自己的内部作用域。每次get_fun使用值调用时,都会创建一个小表来跟踪其中的绑定;即它说,“在这个范围内,value标识符指向被传递的东西。” 该范围在函数执行结束时消失,除非有理由让它徘徊。

如果您要从范围内返回一个函数,那么这就是“范围表”的一部分存在的一个很好的理由——当您稍后调用它时,您返回的函数可能会引用该范围表中的内容。出于这个原因,在Pythonfun中创建的when 会告知' 范围表,以便在需要时方便使用。get_funfunget_funfun

你可以在Python docs on the execution model中阅读更多关于细节和技术术语(我稍微软化了一点)的信息。您还可以使用 来查看函数引用的封闭范围的部分print fun.__closure__。在上面,我们看到了对 的引用value,它恰好是一个 int:

# Same as before, commented out for emphasis.
#functions = []
#for number in range(1, 6):
#    functions.append(get_fun(number))
#assert [fun() for fun in functions] == range(1, 6)
print functions[0].__closure__
# Produces: (<cell at 0x8dc30: int object at 0x1004188>,)
于 2009-01-17T11:13:42.747 回答
3

尝试使用 () 而不是 []:

listOfLambdas = (lambda: square(i) for i in listOfNumbers)

你会得到:

1
4
9
16
25
于 2014-05-15T18:58:02.863 回答
2
listOfLambdas = [lambda i=i: square(i) for i in listOfNumbers]

或者

listOfLambdas = map(lambda i: lambda: square(i), listOfNumbers)
于 2009-01-17T01:52:49.790 回答
1

我有时发现为函数对象定义实际的类可以更容易理解发生了什么:

>>> class square(object):
...   def __init__(self, val):
...     self.val = val
...   def __call__(self):
...     return self.val * self.val
...
>>> l = [1,2,3,4,5]
>>> funcs = [square(i) for i in l]
>>> for f in funcs:
...   print f()
...
1
4
9
16
25
>>>

诚然,它比使用 lambda 或闭包要冗长一些,但是当我尝试用函数做一些不明显的事情时,我发现这更容易理解。

于 2009-01-17T01:46:37.633 回答
1

这将解决您的问题:

import copy

listOfNumbers = [1,2,3,4,5]
square = lambda x: x * x
listOfLambdas = [lambda num=copy.deepcopy(i): square(num) for i in 
listOfNumbers]

for f in listOfLambdas:
    print( f())
于 2021-08-21T12:54:35.697 回答
0

你也可以这样做:

>>> def squares():
...     for i in [1,2,3,4,5]:
...         yield lambda:i*i
... 
>>> print [square() for square in squares()]
[1, 4, 9, 16, 25]
于 2015-07-14T04:15:05.690 回答
0

作为补充评论,我想概述从 sympy 矩阵生成 lambda 函数列表的可能性(我不知道这是否是最好的方法,但我就是这样做的,我觉得它很方便):

import sympy as sp
sp.var('Ksi')
# generate sympy expressions for Berstein's polynomials
B_expr = sp.Matrix([sp.binomial(3, i) * Ksi**i * (1 - Ksi)**(3-i) for i in range(4)])
# lambdify them 
B = [sp.lambdify((Ksi), B_expr[i]) for i in range(4) ]
于 2017-01-10T08:02:39.067 回答