为了账号安全,请及时绑定邮箱和手机立即绑定

(lambda)函数闭包捕获了什么?

(lambda)函数闭包捕获了什么?

最近我开始玩Python,我遇到了一些特殊的闭包方式。请考虑以下代码:adders=[0,1,2,3]for i in [0,1,2,3]:    adders[i]=lambda a: i+aprint adders[1](3)它构建了一个简单的函数数组,它接受单个输入并返回由数字添加的输入。函数在for循环中构造,迭代器i从中循环0到3。对于这些数字中的每一个,lambda都会创建一个函数i,该函数捕获并将其添加到函数的输入中。最后一行将第二个lambda函数3作为参数调用。令我惊讶的是输出结果是6。我期待一个4。我的理由是:在Python中,一切都是一个对象,因此每个变量都是指向它的指针。在创建lambda闭包时i,我希望它存储一个指向当前指向的整数对象的指针i。这意味着当i分配一个新的整数对象时,它不应该影响先前创建的闭包。遗憾的是,adders在调试器中检查数组表明它确实存在。所有的lambda功能指的最后一个值i,3,这将导致adders[1](3)返回6。这让我想知道以下内容:闭包捕获的内容是什么?什么是最优雅的方式来说服lambda函数以更改其值i时不会受到影响的方式捕获当前i值?
查看完整描述

4 回答

?
慕后森

TA贡献1802条经验 获得超5个赞

您可以使用具有默认值的参数强制捕获变量:


>>> for i in [0,1,2,3]:

...    adders[i]=lambda a,i=i: i+a  # note the dummy parameter with a default value

...

>>> print( adders[1](3) )

4

我们的想法是声明一个参数(巧妙地命名i)并给它一个你想要捕获的变量的默认值(值  i)


查看完整回答
反对 回复 2019-05-27
?
慕斯王

TA贡献1864条经验 获得超2个赞

为了完整性,您对第二个问题的另一个答案是:您可以在functools模块中使用partial

通过从运营商导入add,Chris Lutz提出的示例变为:

from functools import partialfrom operator import add   # add(a, b) -- Same as a + b.adders = [0,1,2,3]for i in [0,1,2,3]:
   # store callable object with first argument given as (current) i
   adders[i] = partial(add, i) print adders[1](3)


查看完整回答
反对 回复 2019-05-27
?
qq_笑_17

TA贡献1818条经验 获得超7个赞

请考虑以下代码:

x = "foo"def print_x():
    print x

x = "bar"print_x() # Outputs "bar"

我想大多数人都不会发现这种混乱。这是预期的行为。

那么,为什么人们认为它在循环中完成时会有所不同?我知道我自己犯了这个错误,但我不知道为什么。这是循环?或者也许是lambda?

毕竟,循环只是一个较短的版本:

adders= [0,1,2,3]i = 0adders[i] = lambda a: i+a
i = 1adders[i] = lambda a: i+a
i = 2adders[i] = lambda a: i+a
i = 3adders[i] = lambda a: i+a


查看完整回答
反对 回复 2019-05-27
?
慕运维8079593

TA贡献1876条经验 获得超5个赞

你的第二个问题已得到解答,但至于你的第一个问题:

封闭捕获到底是什么?

Python中的范围是动态的和词汇的。闭包将始终记住变量的名称和范围,而不是它指向的对象。由于示例中的所有函数都在同一作用域中创建并使用相同的变量名,因此它们始终引用相同的变量。

编辑:关于如何克服这个问题的另一个问题,有两种方法可以想到:

  1. 最简洁但不严格等同的方式是Adrien Plisson推荐的方式。使用额外参数创建lambda,并将额外参数的默认值设置为要保留的对象。

  2. 每次创建lambda时,创建一个新范围会更冗长但更少hacky:

    >>> adders = [0,1,2,3]>>> for i in [0,1,2,3]:...     adders[i] = (lambda b: lambda a: b + a)(i)...     >>> adders[1](3)4>>> adders[2](3)5

    这里的范围是使用一个新函数(lambda,为简洁起见)创建的,它绑定了它的参数,并传递你想要绑定的值作为参数。但是,在实际代码中,您很可能会使用普通函数而不是lambda来创建新范围:

    def createAdder(x):
        return lambda y: y + x
    adders = [createAdder(i) for i in range(4)]


查看完整回答
反对 回复 2019-05-27
  • 4 回答
  • 0 关注
  • 1207 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信