最近,我开始使用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并将其添加到函数的输入中.最后一行使用3作为参数调用第二个lambda函数.令我惊讶的是,yields 是6.

我原以为是4分.我的推理是:在Python中,一切都是对象,因此每个变量都是指向它的指针.在为i创建lambda个闭包时,我预计它会存储一个指向i当前指向的整数对象的指针.这意味着当i分配一个新的整数对象时,它应该不会影响之前创建的闭包.遗憾的是,判断调试器中的adders数组会发现确实如此.所有lambda个函数都引用最后一个值i,3,这会导致adders[1](3)返回6.

这让我想知道:

  • 闭包到底捕获了什么?
  • 说服lambda个函数捕捉i当前值的最优雅方式是什么?当i改变其值时,这种方式不会受到影响?

推荐答案

你的第二个问题已经得到了回答,但关于你的第一个问题:

闭包到底捕获了什么?

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

关于你的另一个问题--如何克服这个问题,我脑海中浮现出两种方法:

  1. 最简洁、但严格意义上不等同的方法是one recommended by Adrien Plisson.创建一个带有额外参数的lambda,并将额外参数的默认值设置为要保留的对象.

  2. 在每次创建lambda时,创建一个新的作用域会更详细一些,但不那么麻烦:

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

Python相关问答推荐

在两极中实施频率编码

Twilio:CallInstance对象没有来自_的属性'

如何使用Tkinter创建两个高度相同的框架(顶部和底部)?

如何将Matplotlib的fig.add_axes本地坐标与我的坐标关联起来?

将行从一个DF添加到另一个DF

Python -根据另一个数据框中的列编辑和替换数据框中的列值

Pandas :多索引组

如何在Python中使用时区夏令时获取任何给定本地时间的纪元值?

根据网格和相机参数渲染深度

更改matplotlib彩色条的字体并勾选标签?

"使用odbc_connect(raw)连接字符串登录失败;可用于pyodbc"

在极性中创建条件累积和

如何在UserSerializer中添加显式字段?

如何在图中标记平均点?

启用/禁用shiny 的自动重新加载

为什么我的sundaram筛这么低效

为什么t sns.barplot图例不显示所有值?'

Js的查询结果可以在PC Chrome上显示,但不能在Android Chrome、OPERA和EDGE上显示,而两者都可以在Firefox上运行

如何使用加速广播主进程张量?

ModuleNotFoundError:Python中没有名为google的模块''