Python中的闭包是什么?
目录
- 作用域(scope)
- 闭包的定义
- 闭包的作用
- 总结
2018.4.15
更新了对于函数作用域的理解内容
1.作用域
作用域限定了一个变量在程序中的有效范围,对于一个函数,其内部定义的变量的作用域就是这个函数体内部,一旦函数结束被调用,此变量将被析构。简言之,函数中定义的变量称作局部变量,它只能在函数内部被引用。
下面的例子说明了局部变量不能在函数外访问:
In [9]: def f1():
...: a = 1
...:
In [10]: print(a)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-10-bca0e2660b9f> in <module>()
----> 1 print(a)
NameError: name 'a' is not defined
而在所有函数之外定义的变量可以被函数访问:
In [12]: b = 1
In [13]: def f2():
...: print(b)
...:
In [14]: f2()
1
另外,由于Python支持函数的嵌套,按照函数的作用域原则,内函数可以访问外函数定义的变量:
In [37]: def f1():
...: x = 1
...: def f2():
...: print(x)
...: f2()
...:
In [38]: f1()
1
2.闭包的定义
我们将上一个代码稍作修改:
In [50]: def f1():
...: x = 1
...: def f2():
...: print(x)
...: return f2
...:
In [51]: m = f1()
In [52]: n = f1()
In [53]: m == n
Out[53]: False
In [54]: m()
Out [54]: 1
- 从上面的例子可以看出,f1每次返回的函数都不同。
- 还有一个隐含特性,在In [54]: x()这条代码中,尽管随着函数f1()被调用,参数x的作用域已经结束,然而函数m仍然可以正常的引用参数x,这表明了内嵌函数可以引用外部函数的参数,并且当这些参数的作用域随着外部函数的调用结束后仍然能被内嵌函数引用。
通过上述的例子,我们再引入Wiki百科的对于闭包的定义:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
如果要在内嵌函数中修改引用的外部变量,需要在内嵌函数中对要修改的变量使用nonlocal关键字进行声明:
In [58]: def creatCounter(): # 实现一个加法器 每次调用内嵌函数就加一
...: i = 0
...: def count():
...: nonlocal i # nonlocal关键字标志着内嵌函数将修改外部变量 如果没有这行代码将会出错
...: i += 1
...: return i
...: return count
...:
In [59]: f = creatCounter()
In [60]: f()
Out[60]: 1
In [61]: f()
Out[61]: 2
In [62]: f()
Out[62]: 3
3.闭包的作用
闭包避免使用了全局变量,闭包允许将某些数据和函数关联起来,这一点很像类。在面向对象过程中,我们将定义了一些属性,并将它们与一些方法关联起来。如果要定义只用一个方法的类,可以采用闭包的方法实现。另外,闭包在装饰器中也很重要。
判断一个函数是不是闭包,可以查看它的closure属性,如果是闭包,查看该属性将会返回一个cell对象组成的元组,分别对每个cell对象查看其cell_contents属性,返回的内容就是闭包引用的自由变量的值。下面通过一个例子展示:
In [41]: def add(x,y):
...: def f(z):
...: return x+y+z
...: return f
...:
In [42]: d = add(5,5)
In [43]: d(9)
Out[43]: 19
In [44]: d(1)
Out[44]: 11
In [45]: d.__closure__ # 闭包的__closure__属性
Out[45]:
(<cell at 0x000001F9A295CCD8: int object at 0x000000006F666140>,
<cell at 0x000001F9A295C9A8: int object at 0x000000006F666140>)
In [46]: for i in d.__closure__:
...: print(i.cell_contents) # 查看每个cell对象的内容 —— cell_contents属性
...:
5
5
- cell_contents解释了局部变量在脱离函数后仍然可以在函数之外被访问,因为变量被存储在cell_contents中了。
4.总结
- 在python的作用域规则里面,创建变量一定会在当前作用域里创建一个变量,但是访问或者修改变量时会先在当前作用域查找变量,没有找到匹配变量的话会依次向上在闭合的作用域里面进行查找。
- 在内嵌函数中使用nonlocal关键字对外部变量进行声明,可以允许内嵌函数修改外部变量。
- 闭包是引用了自由变量的函数。
- 闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
- 闭包中包含了内部函数的代码,以及所需外部函数中的变量的引用。其中所引用的变量称作上值(upvalue)。
- 判断一个函数是不是闭包,可以查看它是否存在closure属性
- closure属性是一个包含若干个cell对象的元组,每个cell对象的cell_content属性分别代表一个自由变量的值。
结语:这篇文章更多的只是作为学习笔记,其中参考了网上一些前辈的文章或是其他资料,所以有些可能会大量引用,如有冒犯,请私信我,还望谅解。