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

2024-09-28 22:23:46 发布

您现在位置:Python中文网/ 问答频道 /正文

我想从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都被赋予了错误的参数。我做错什么了吗?有办法解决吗?我想我在Python2.4中。

编辑:再尝试一下,就得出这样的结论:

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

打印1到25之间的预期正方形,但使用前面的打印语句:

for f in listOfLambdas:
    print f(),

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

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


Tags: 对象lambdain列表for参数错误action
3条回答

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

在您的代码片段中,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标识符指向传递的内容。”这个范围在函数执行结束时消失,除非有理由让它挂起。

如果要从作用域内返回函数,这是“作用域表”的某些部分挂起的一个很好的原因——稍后调用时,要返回的函数可能会引用该作用域表中的内容。因此,当在get_fun中创建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>,)

我猜你在列表理解中创建的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它实际上不会解析该值,直到它被调用。这既令人困惑,又很强大。

你有:

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

相关问题 更多 >