如何创建Python lambdas列表(在列表解析/ for循环中)?

时间:2009-01-17 01:19:14

标签: python lambda scope closures list-comprehension

我想从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个。现有的lambda对象如何在这两个打印调用之间发生变化?

相关问题:Why results of map() and list comprehension are different?

8 个答案:

答案 0 :(得分:23)

你有:

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。这就是魔术发生的地方。

答案 1 :(得分:19)

我猜你在列表推导中创建的lambda绑定到变量i,最终结果为5.因此,当你在事实之后评估lambdas时,它们都被绑定到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 ,它实际上不会解析该值,直到它被调用。这既困惑又强大。

答案 2 :(得分:4)

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

在您的代码段中,lambdas绑定到全局范围,因为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

我知道,心灵都在吹。 Anywho,凭借我们新发现的知识,我们可以确定您创建的lambdas是指在封闭范围内绑定的(单个)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标识符指向传递的东西。”该范围在函数执行结束时消失,除非有理由让它闲置。

如果你从一个范围内返回一个函数,那么“范围表”的某些部分就会出现这个问题的好理由 - 当你稍后调用它时,你返回的那个函数可以引用那个范围表中的东西。上。出于这个原因,在fun内创建get_fun时,Python会告诉fun关于get_fun的范围表,fun在需要时保持方便。< / p>

您可以在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>,)

答案 3 :(得分:2)

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

或者

listOfLambdas = map(lambda i: lambda: square(i), listOfNumbers)

答案 4 :(得分: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
>>>

当然,它比使用lambdas或闭包更加冗长,但是当我尝试用函数做非显而易见的事情时,我发现这更容易理解。

答案 5 :(得分:1)

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

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

你会得到:

1
4
9
16
25

答案 6 :(得分: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]

答案 7 :(得分: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) ]