背景
本文尝试介绍Python中的闭包(closure),包括闭包是什么? 为什么要使用闭包?如何使用闭包?
嵌套函数及非局部变量
在介绍闭包之前,需要先明白什么是嵌套函数和非局部变量。在一个函数(fun1)中定义的另一个函数(fun2)称为嵌套函数。嵌套函数(fun1)可以访问外围作用域的变量,即为非局部变量。换一句话说,嵌套函数能够访问enclosing scope(闭包作用域,或者外围作用域,或者外层作用域)下的变量。这些非局部变量默认情况下是只读的,为了修改它们,必须显式地将它们声明为非局部变量(使用nonlocal
关键字)。具体示例如下:
def print_msg(msg):
# 这是外层函数
def printer():
# 这是嵌套函数
print(msg)
printer()
# 调用外层函数
print_msg("Hello World")
输出:
Hello World
可以看出,嵌套函数printer()
是可以访问到非局部变量msg
,即外层函数中的msg
。
定义一个闭包函数
如果上述示例print_msg()
函数的最后一行不是调用printer()
,而是返回printer
呢?具体代码改成如下:
def print_msg(msg):
def printer():
# 这是一个嵌套函数
print(msg)
return printer # 返回嵌套函数
# Now let's try calling this function.
# 输出: Hello World
another = print_msg("Hello World")
another()
可以看出输出结果是:
Hello World
这里调用print_msg()
函数的时候传入了字符串Hello World
,返回函数名为another
。在调用another()
时,之前传入的字符串Hello World
信息仍然保留,尽管我们已经完成了print_msg()
函数的执行。
这种将一些数据(比如上述示例中的Hello World
)附加到代码中的技术在Python中称为闭包。enclosing scope中的值会被存储,即使该变量超出作用域,或者函数本身被从当前命名空间中删除,也会记住外围作用域中的这个值。比如:
del print_msg
another()
依然能够输出:
Hello World
此时尝试调用:
print_msg("Hello")
报错如下:
File "test.py", line 306, in <module>
print_msg("Hello World")
NameError: name 'print_msg' is not defined
因为返回函数仍然可以正常工作,而原始的函数已经被删除,无法像之前那样起作用。
什么是闭包
正如上面的例子所示,当嵌套函数在其外围作用域中引用一个值时,就出现了一个闭包。在Python中创建闭包,须满足的条件总结如下:
- 必须有一个嵌套函数(函数中有函数)
- 嵌套函数必须引用外围函数中定义的值
- 外围函数必须返回嵌套函数
什么时候使用闭包
闭包可以避免使用全局变量,并提供某种形式的数据隐藏。它还可以为这个问题提供一个面向对象的解决方案。当类中需要实现的方法很少(大多数情况下只有一个方法)时,闭包可以提供一种替代的、更优雅的解决方案。但是,当属性和方法的数量比较多时,最好实现一个类。
下面是一个简单的例子,闭包可能比定义类和创建对象更合适。
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)
# 输出: 27
print(times3(9))
# 输出: 15
print(times5(3))
# 输出: 30
print(times5(times3(2)))
另外,需要注意的是:闭包函数中包含的值是可以找到的。所有的函数对象都有__closure__
属性。当是闭包函数时,该属性返回cell objects的元组。以上述示例进行说明,其中times3
和times5
都是闭包函数,打印输出:
print(make_multiplier_of.__closure__)
print(times3.__closure__)
输出结果如下:
None
(<cell at 0x7f5c8d8cb618: int object at 0xa68b00>,)
cell对象中的属性cell_contents
存储着闭包值:
print(times3.__closure__[0].cell_contents)
print(times5.__closure__[0].cell_contents)
输出结果如下:
3
5