闭包是什么
使用像javascript和python这样支持面向对象范式的语言进行编程时,都会涉及到闭包的概念以及闭包的使用。我们今天就从这两个方面来讨论一下闭包(本文仅针对python3):
首先是维基百科中关于闭包的概念:
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。
根据这句话,其实我们自己就可以总结出在python语言中形成闭包的三个条件,缺一不可:
1)必须有一个内嵌函数(函数里定义的函数)——这对应函数之间的嵌套
2)内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量
3)外部函数必须返回内嵌函数——必须返回那个内部函数
前两个条件我们比较好理解,那什么会有第三条规定呢?其实闭包一词指的就是上文中提到的那个“内部的函数”,我们下面就会发现,只有那个内部函数才有所谓的__closure__属性。
我们首先来创造一个闭包:
def funx():
x=5
def funy():
nonlocal x
x+=1
return x
return funy
我们根据上面的三准则创造了一个函数,其中的funy就是所谓的闭包,而funy内部所引用过的x就是所谓的闭包变量。
闭包有什么用
我们在交互式模式下,对上面的函数进行简单的测试:
>>> a=funx()
>>> a()
6
>>> a()
7
>>> a()
8
>>> a()
9
>>> x
Traceback (most recent call last):
File "<pyshell#19>", line 1, in <module>
x
NameError: name 'x' is not defined
>>>
我们会发现,funx中的x变量原本仅仅是funx的一个局部变量。但是形成了闭包之后,它的行为就好像是一个全局变量一样。但是最后的错误说明x并不是一个全局变量。其实这就是闭包的一个十分浅显的作用,形成闭包之后,闭包变量能够随着闭包函数的调用而实时更新,就好像是一个全局变量那样。(注意我们上面的a=funx(),a实际上应该是funy,所以a称为闭包)
进一步探究
我们能否找出点证据证明我们对于闭包的猜想呢?很简单,我们可以尝试下面的操作:
>>> a.__closure__
(<cell at 0x0000002F346FB408: int object at 0x00000000667D02D0>,)
>>> type(a.__closure__)
<class 'tuple'>
>>> type(a.__closure__[0])
<class 'cell'>
>>> a.__closure__[0].cell_contents
9
>>> a()
10
>>> a.__closure__[0].cell_contents
10
>>> def test():pass
>>> test.__closure__==None
True
>>>
这样我们就明白了,形成闭包之后,闭包函数会获得一个非空的__closure__属性(对比我们最后的函数test,test是一个不具备闭包的函数,它的__closure__属性是None),这个属性是一个元组。元组里面的对象为cell对象,而访问cell对象的cell_contents属性则可以得到闭包变量的当前值(即上一次调用之后的值)。而随着闭包的继续调用,变量会再次更新。所以可见,一旦形成闭包之后,python确实会将__closure__和闭包函数绑定作为储存闭包变量的场所。