1.内嵌函数的非本地变量
在另一个函数里面定义的函数,被称为内嵌函数。内嵌函数可以访问闭合范围内(就是外部函数范围)的变量,这些变量被称为非本地变量(nonlocal variable)。
默认情况下,非本地变量是只读的。为了可以修改非本地变量,需要将它们生命为nonlocal,如下例所示。
def print_msg(msg): """This is the outer enclosing function""" def printer(): """This is the nested function""" print(msg) printer() # We execute the function # Output: Hello print_msg("Hello")
可以看到,内嵌函数是printer(),可以访问非本地变量msg,msg定义在外部函数print_msg()里面。
2.定义一个闭包函数
在上面的例子中,如果print_msg()返回print()函数,而不是调用它,会发生什么?这要求函数被这样定义
def print_msg(msg): """This is the outer enclosing function""" def printer(): """This is the nested function""" print(msg) return printer # this got changed # Now let's try calling this function. # Output: Hello another = print_msg("Hello") another()
这非同寻常。
print_msg()函数被通过传入“Hello”所调用,返回的函数被绑定为another. 在调用another()的时候,我们对print_msg()函数已经完成调用了,但是“Hello”仍然被记住了!
这种将一些数据("Hello")附加到代码的技术,被称为python里面的closure.
闭合范围内的数据(非本地变量)能够一直被记住,即便它们已经脱离了闭合范围或者外部函数已经被从命名空间删除.
在python shell里面继续运行下面的代码,看看会发生什么.
>>> del print_msg >>> another() Hello >>> print_msg("Hello") Traceback (most recent call last): ... NameError: name 'print_msg' is not defined
3.怎样得到一个闭包函数?
从上面的例子可以看出,当我们让内嵌函数引用一个非本地变量,就得到了一个python closure.
python closure必须满足以下三点标准:
1)必须有一个内嵌函数(函数里定义的函数)
2)内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量
3)外部函数必须返回内嵌函数
4.什么时候使用closure?
closure适合做什么?
closure可以减少使用全局变量和提供一定程度的数据隐藏.
当一个类只有很少的方法(通常是一个),closure可以提供一种更优雅的替代方案。但如果类的属性或者方法开始增多,最好还是实现一个类。
下面是一个closure也许比类更好的一个例子。当然,到底哪个更好最终还是取决与你。
def make_multiplier_of(n): def multiplier(x): return x * n return multiplier # Multiplier of 3 times3 = make_multiplier_of(3) # Multiplier of 5 times5 = make_multiplier_of(5) # Output: 27 print(times3(9)) # Output: 15 print(times5(3)) # Output: 30 print(times5(times3(2)))
python的装饰器可以扩展closure的功能,详情戳这里.
5.获取闭合数值
最后还有一个友情提示,所有在外部函数定义的非本地变量,都可以被获取到。
所有的函数对象都有一个__closure__属性,如果它是一个闭包函数,那么它包含一个cell objects元组。
就上面的例子,我们知道time3和times5是闭包函数
>>> make_multiplier_of.__closure__ >>> times3.__closure__ (<cell at 0x0000000002D155B8: int object at 0x000000001E39B6E0>,)
cell object有cell_contents属性,保存了闭合数值
>>> times3.__closure__[0].cell_contents 3 >>> times5.__closure__[0].cell_contents 5