闭包
前言
闭包包并不是python中特有的概念,在函数式编程语言中都有,例如 Javascript
、Ruby
这样的语言中都存在闭包。
闭包对初学者来说绝对是一个晦涩难懂的概念,即使你去看权威的维基百科,他的描述也像论文一样难懂,我们来看看他是怎么说的。
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。
看完后是不是一愣一愣的,说的啥,每个字都认识,就是不理解。
其实啊,闭包本质上其实还是个函数,只不过这个函数比较特殊,特殊在哪呢?
普通函数
先来看个普通函数
1 | def simple_adder(x, y): |
x
和 y
作为形参传入函数 simple_adder
,这两个参数可以理解为函数的局部变量,函数被调用时,会在栈上创建其执行环境,函数执行完成后,局部变量随着栈帧的销毁被垃圾回收,局部变量无法在外部引用。
闭包函数
再来看这个复杂一点的函数(假设你对函数是一等对象这个概念已经有所了解)
1 | def adder(x): |
现在定义一个函数 adder
, 然后还有一个嵌套函数 func
。外层函数的返回值是嵌套函数func
对象。(不知道什么是一等对象的先看文末指引)
执行:
1 | add2 = adder(2) |
对于普通函数来说,调用完adder(2)这个函数时,局部变量x的值2本应该销毁了,但是当我们执行第二行add2(3)
的时候,这个2居然还活着,其实这就是闭包最神奇的地方,因为它能访问自由变量x,即使x所在的环境(adder函数)已经不再了。
这里的嵌套函数func
就是一个“闭包”函数。
闭包使得函数可以绑定一个状态值(属性),比如这里 add2
这个函数就绑定属性值2,他会一直跟随着这个闭包函数。
闭包只在函数式编程语言中才有,对于纯面向对象语言,比如Java就没法实现上面的效果,如果要实现必须用类
来实现。因为面向对象中,类天然就可以附带属性(状态)
如果把前面的闭包用类来实现类似的效果,可以通过如下写法实现, 其中x
就是类Adder
的一个状态属性,2
这个状态值会一直跟着add2这个实例对象
1 | class Adder: |
什么时候使用闭包
1、可以用来替换类, 假设一个类中就只有一个方法,那么这时候用闭包来实现更优雅,比如前面例子
2、替换全局变量,上面例子中,如果不要闭包来实现,另一个做法就是定义一个全部变量,这样任何时候都能访问到,但是全局变量的弊端也在于此,不安全,因为他暴露在外面,有可能在其他地方被修改。而闭包相当于把变量隐藏起来,外部是没法去访问的,只有闭包自己可以访问。
其实你们经常用的装饰器就是闭包的一种应用
__closure__
属性
在python中,每个函数都有个属性叫__closure__
,通过它我们可以检查函数是不是一个闭包函数
1 | print(simple_adder.__closure__) # None |
__closure__
返回值为None,那肯定不是闭包
1 | print(add5.__closure__[0].cell_contents) # 2 |
而 add5
是一个闭包,__closure__
返回的是一个只读的tuple对象,cell_contents 是该函数绑定的自由变量。