该文章只是一篇对闭包和装饰器的简单介绍。
一开始看裘宗燕老师所写的《程序员学Python》这本书时,对闭包十分不理解,琢磨着为什么要在一个函数中返回一个函数呢?只能做个标记继续往后看,直到看到装饰器这一章时,细细思考其中原理方才发现闭包的重要性。
1. 什么是装饰器
装饰器(decorator),是Python语言的一个语法糖(Syntactic sugar)。 装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
比如有这么一个装饰器decorator:
@decorator
def func(param):
...
那么该代码等价于:
func = decorator(func)
即,装饰器接受一个函数作为参数,在内部对函数增添一些功能后,再返回装饰后的函数对象,并赋值给原来的变量。
2. 由装饰器到闭包
一个函数,且里面再包含一个函数(内部函数),那么该内部函数就是一个闭包(closure)。
我们先定义一个装饰器:
import time
from functools import wraps
def timethis(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end - start)
return result
return wrapper
@timethis
def func(n):
while n >= 0:
n -= 1
在解释器中查看结果:
>>> func(1999999)
func 0.10681343078613281
在这里,函数timethis()
所包含的内部函数wrapper()
就是一个闭包,而外部函数timethis()
则是一个装饰器。经过该装饰器包装过的函数,支持运行时间记录的功能。其原理也很简单:将原函数func
作为一个参数传进函数timethis()
中,其内部函数wrapper()
加上特定的功能后,再调用原来的函数func
。最后,外部函数timethis()
在返回闭包wrapper()
给外部的func
变量。从此,func
变量不再是原来的函数对象,而是经过装饰后的、新的函数对象。在这里,我们就可以窥见闭包的重要作用。
除此之外,闭包的作用域也是一个重要的话题。闭包能够访问函数体之外定义的非全局变量(简单来说,就是外部函数定义的变量它也是可以访问的),而这个非全局变量指的就是自由变量。可以这么说,闭包就是一个拥有状态的函数:
修改上面的例子以说明:
import time
from functools import wraps
def timethis(func):
time_history = []
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal time_history
start = time.time()
result = func(*args, **kwargs)
end = time.time()
exec_time = end - start
print(func.__name__, exec_time)
time_history.append(exec_time)
print("history: ", time_history)
return result
return wrapper
@timethis
def func(n):
while n >= 0:
n -= 1
在这里,装饰器增加了一个功能,能够保存历史的运行时间(需要使用nonlocal
将time_history
变成自由变量)。我们来测试一下是否可行:
>>> func(10000)
func 0.0005609989166259766
history: [0.0005609989166259766]
>>> func(2333)
func 0.0001125335693359375
history: [0.0005609989166259766, 0.0001125335693359375]
>>> func(233333)
func 0.01238703727722168
history: [0.0005609989166259766, 0.0001125335693359375, 0.01238703727722168]
从中可以看到,闭包是可以保存状态的!这个和普通的函数还是有点差异的,跟静态函数(static method)有那么一点点类似。
3. 总结
总的来说,闭包就是藏在一个函数里的函数,由于这一点,它相比普通函数拥有保存状态的功能。因此,它也是装饰器的重要基础。
当然,装饰器也是可以使用类来实现的。这里就不多说了。更多细节部分可以参考Python文档或者《Python Cookbook(第三版)》。