Python学习之路(3)——闭包
今天将一个很有意思的概念,叫做闭包。
这里先引用一段Wikipedia里对闭包的描述:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
闭包的概念出现于60年代,最早实现闭包的程序语言是Scheme。之后,闭包被广泛使用于函数式编程语言如ML语言和LISP。很多命令式程序语言也开始支持闭包。
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。运行时,一旦外部的 函数被执行,一个闭包就形成了,闭包中包含了内部函数的代码,以及所需外部函数中的变量的引用。其中所引用的变量称作上值(upvalue)。
可以从这段描述中了解到,闭包是和变量和嵌套函数有关的,根据字面意思,可以形象地把闭包理解为一个封闭的包裹,而这样的一个包裹其实就是一个函数,而函数内部也就是包裹的内部是变量,变量是可以随着函数到处游荡的。
那么这里先介绍一下2个概念:变量和嵌套函数。
一、变量:作用域,局部变量和全局变量
作用域是程序在运行时,变量可以被访问的范围。
定义在函数内的变量是局部变量,局部变量的作用域范围是在函数内部,在函数外部不能被访问:
>>> def func1():
num = 100
>>> print(num)
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
print(num)
NameError: name 'num' is not defined
定义在所有函数最外面的变量是全局变量,全局变量的作用域范围是全局范围内的,也就是说在函数内部也可以访问全局变量:
>>> num = 10 >>> def func2(): print(num) >>> func2() 10
二、嵌套函数
函数定义不仅可以在模块的最外层,还可以在另一个函数的内部,这种定义在函数内部的函数称为嵌套函数(nested function),调用外层函数时,运行到的def语句仅仅是完成对内层函数的定义,而不会去调用内层函数,直到在嵌套函数之后显式地对其进行调用:
>>> num = 100
>>> def func1(): num = 10 def func2(): print(num) func2() >>> func1() 10
对于嵌套函数,它可以访问到其外层作用域中声明的变量。
比如示例中,func1中的变量num覆盖了全局变量num=100,而后在嵌套函数func2中的本地变量按照引用规则被引用了变量num=10。
三、闭包
那么再看看嵌套函数的另一个特殊用法:作为函数的返回值。
考虑如下示例:
>>> def func1(): num = 10 def func2(): print(num) return func2 >>> f=func1() >>> print(f) <function func1.<locals>.func2 at 0x0000000003388D08> >>> f() 10
这个例子和前面的例子意义,同样输出了结果10。不同之处在于嵌套函数func2直接作为返回值返回了。
一般情况下,函数中的局部变量仅在函数执行时可用,一旦func1()执行过后,num变量将不再可用。然后,上面的例子中,我们发现func1()执行完之后,在调用f()的时候num变量正常输出了,这就是闭包的作用,闭包使得局部变量在函数外被访问成为可能。
那么闭包的概念就是,当调用了一个函数A,这个函数A返回了一个函数B,那么这个返回的函数B就叫做闭包。
反过来看一下之前Wikipedia上对闭包的定义,可以知道这里函数 f() 就是一个闭包,它有两部分组成,嵌套函数func2()和变量num。闭包使得这些变量的值始终保存在内存中。
四、为什么要使用闭包
闭包的好处就是避免使用全局变量,而且闭包允许将函数与其所操作的某些数据关联起来。有没有发现这个和类很相似,都提供了对数据的封装。不同的是闭包本身就是一个方法。
一般来说,当对象中只有一个方法的时候,使用闭包是更好的选择。
>>> def total(x): def func2(y): return x + y return func2 >>> total10=total(10) >>> total10(10) 20 >>> total10(100) 110
这比类实现起来更加优雅,此外装饰器也是基于闭包的一种应用场景,这就是另外一个话题了,这里先不深入研究。
五、如何辨别一个函数是不是一个闭包
其实对于所有函数来说,都有一个__closure__属性,如果一个韩式是一个闭包的话,那么该属性会返回一个由cell对象组成的元组对象。cell对象中的cell_contents属性就是闭包中的自由变量。
>>> total.__closure__ >>> total10.__closure__ (<cell at 0x00000000032D5E58: int object at 0x000000001DD75440>,) >>> total10.__closure__[0].cell_contents 10
浙公网安备 33010602011771号