如何创建 lambda 列表(在列表理解/for 循环中)?
Posted
技术标签:
【中文标题】如何创建 lambda 列表(在列表理解/for 循环中)?【英文标题】:How do I create a list of lambdas (in a list comprehension/for loop)? 【发布时间】:2010-10-01 22:38:39 【问题描述】:我想从 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 的方格,然后使用前面的打印语句:
for f in listOfLambdas:
print f(),
仍然给我所有25
s。现有的 lambda 对象在这两个打印调用之间有何变化?
相关问题:Why results of map() and list comprehension are different?
【问题讨论】:
我认为值得“推广”@abarnert 下面的评论:这基本上是一个FAQ in the official documentation。 这能回答你的问题吗? What do (lambda) function closures capture? 【参考方案1】:你有:
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
。这就是魔法发生的地方。
【讨论】:
酷。这个“黑客”记录在任何地方吗?有没有更好的方法来做咖喱?另外,请不要再提及您的旋转床。 我从未在任何文档中看到过明确提及它。它利用了函数参数的默认值是在创建函数时分配的这一事实,这已记录在案。 此示例与文档中的 Why do lambdas defined in a loop with different values all return the same result? 几乎相同。 很好的发现。我记不清了,(我不敢相信我已经使用 SO 5 年了!)但我想我是独立构思了这个例子。我想只有几种方法可以用一个例子来解释这种现象。 令人惊讶的是,这个技巧在 12 年后对 Python 3.x 完美运行【参考方案2】:我猜你在列表推导中创建的 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 它实际上并没有解析该值,直到它被调用。这既令人困惑又强大。
【讨论】:
【参考方案3】:当函数语句被执行时,它们被绑定到它们的(词法上的)封闭范围。
在您的 sn-p 中,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
标识符指向被传递的东西。”该作用域在函数执行结束时消失,除非有理由让它徘徊。
如果您要从范围内返回一个函数,这就是“范围表”的一部分存在的一个很好的理由——当您稍后调用它时,您返回的函数可能会引用该范围表中的内容在。出于这个原因,当fun
在get_fun
中创建时,Python 会告诉fun
关于get_fun
的范围表,fun
在需要时可以方便地使用。
您可以在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>,)
【讨论】:
【参考方案4】:尝试使用 () 代替 []:
listOfLambdas = (lambda: square(i) for i in listOfNumbers)
你会得到:
1
4
9
16
25
【讨论】:
【参考方案5】:listOfLambdas = [lambda i=i: square(i) for i in listOfNumbers]
或者
listOfLambdas = map(lambda i: lambda: square(i), listOfNumbers)
【讨论】:
【参考方案6】:我有时发现为函数对象定义实际的类可以更容易理解发生了什么:
>>> 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 或闭包要冗长一些,但当我尝试用函数做一些不明显的事情时,我发现这更容易理解。
【讨论】:
【参考方案7】:这将解决您的问题:
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())
【讨论】:
【参考方案8】:你也可以这样做:
>>> 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]
【讨论】:
【参考方案9】:作为附加评论,我想概述从 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) ]
【讨论】:
以上是关于如何创建 lambda 列表(在列表理解/for 循环中)?的主要内容,如果未能解决你的问题,请参考以下文章