小编典典

lambda 函数闭包捕获了什么?

all

最近我开始玩 Python,我发现了闭包工作方式的一些特殊之处。考虑以下代码:

adders=[None, None, None, None]

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

print adders[1](3)

它构建了一个简单的函数数组,这些函数接受单个输入并返回该输入加上一个数字。这些函数在for迭代器i0到运行的循环中构造3。对于这些数字中的每一个,lambda都会创建一个函数,该函数捕获i并将其添加到函数的输入中。最后一行调用第二个lambda函数3作为参数。令我惊讶的是,输出是6.

我期待一个4. 我的理由是:在 Python 中,一切都是对象,因此每个变量都是指向它的指针。在为
创建lambda闭包时i,我希望它存储一个指向当前指向的整数对象的指针i。这意味着当i分配一个新的整数对象时,它不应该影响先前创建的闭包。可悲的是,adders在调试器中检查数组表明确实如此。所有lambda函数都引用
, 的最后一个值i3这会导致adders[1](3)返回6

这让我想知道以下几点:

  • 闭包究竟捕获了什么?
  • 说服函数以在更改其值时不会受到影响的方式lambda捕获当前值的最优雅方法是什么?i``i

阅读 97

收藏
2022-04-12

共1个答案

小编典典

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

闭包到底捕获了什么?

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

关于如何克服这个问题的另一个问题,我想到了两种方法:

  1. 最简洁但不是严格等效的方法是Adrien Plisson 推荐的方法。创建一个带有额外参数的 lambda,并将额外参数的默认值设置为您想要保留的对象。

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

>>> 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

  1. 每次创建 lambda 时都创建一个新作用域,这样更冗长但不那么 hacky:
     >>> adders = [0,1,2,3]
    

    for i in [0,1,2,3]:
    … adders[i] = (lambda b: lambda a: b + a)(i)

    adders1
    4
    adders2
    5

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

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