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

Sma*_*ery 35 python lambda closures scope list-comprehension

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

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

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

for f in listOfLambdas:
    print f(),
Run Code Online (Sandbox Code Playgroud)

我希望它会打印出来

1 4 9 16 25
Run Code Online (Sandbox Code Playgroud)

相反,它打印:

25 25 25 25 25
Run Code Online (Sandbox Code Playgroud)

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

编辑:更多的尝试事情,并提出了这样的想法:

listOfLambdas = []
for num in listOfNumbers:
    action = lambda: square(num)
    listOfLambdas.append(action)
    print action()
Run Code Online (Sandbox Code Playgroud)

将预期的正方形从1打印到25,然后使用之前的print语句:

for f in listOfLambdas:
    print f(),
Run Code Online (Sandbox Code Playgroud)

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

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

rec*_*ive 26

你有:

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

for f in listOfLambdas:
    print f()
Run Code Online (Sandbox Code Playgroud)

输出:

25
25
25
25
25
25
Run Code Online (Sandbox Code Playgroud)

你需要讨好!除了美味,使用此默认值"黑客".

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

for f in listOfLambdas:
    print f()
Run Code Online (Sandbox Code Playgroud)

输出:

0
1
4
9
16
25
Run Code Online (Sandbox Code Playgroud)

请注意i=i.这就是魔术发生的地方.

  • 此示例几乎与[为什么在循环中定义的具有不同值的lambda都返回相同的结果?](https://docs.python.org/3.4/faq/programming.html#why-do-lambdas-defined在一个具有不同值的循环中,所有结果均返回相同的结果)。 (3认同)
  • 凉.这个"黑客"记录在哪里吗?有没有更好的方法来做cur?另外,请不要再提起你的旋转床了. (2认同)

Dav*_*Ray 23

我猜你在列表推导中创建的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()
Run Code Online (Sandbox Code Playgroud)

这给了我预期的输出:

1
4
9
16
25
Run Code Online (Sandbox Code Playgroud)

另一种思考方式是lambda在其创建时"捕获"其词汇环境.所以,如果你给它num,它实际上不会解析该值,直到它被调用.这既困惑又强大.


cdl*_*ary 7

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

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

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

因此,当您在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
Run Code Online (Sandbox Code Playgroud)

列表理解的相同处理:

[num for num in range(1, 6)]
assert num == 5
Run Code Online (Sandbox Code Playgroud)

脑洞大开,我知道。任何人,凭借我们新发现的知识,我们可以确定您正在创建的 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)
Run Code Online (Sandbox Code Playgroud)

这是更酷的部分:

# 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) 
Run Code Online (Sandbox Code Playgroud)

因此,解决这个问题的方法当然是为每个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)
Run Code Online (Sandbox Code Playgroud)

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

如果您从作用域内返回一个函数,这就是“作用域表”的一部分闲置的一个很好的理由——当您稍后调用它时,您返回的该函数可以引用该作用域表中的内容。出于这个原因,funget_funPython 中创建的时间告诉funaboutget_fun的范围表,这fun在需要时很方便。

您可以在有关执行模型Python 文档中阅读更多关于细节和技术术语(我稍微软化了一些)的信息。您还可以查看函数引用的封闭作用域的部分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>,)
Run Code Online (Sandbox Code Playgroud)


小智 5

尝试使用 () 代替 []:

listOfLambdas = (lambda: square(i) for i in listOfNumbers)
Run Code Online (Sandbox Code Playgroud)

你会得到:

1
4
9
16
25
Run Code Online (Sandbox Code Playgroud)