Python装饰器
Python装饰器
一个生动的比喻是,被装饰函数是画作,而装饰器就是外面的画框。装饰器为被装饰函数提供额外的功能和行为;可以提取多个函数的共同部分作为装饰器,从而使代码更加清晰。
学习装饰器要逐步解决的问题:
- 如何定义,使用装饰器?装饰器的执行顺序如何?
- 装饰器的接口是怎么样的?被装饰函数及其参数是如何传递的?
- 装饰器的高级用法:参数化装饰器和类装饰器
装饰器的基本知识
理解装饰器要首先理解闭包。首先什么是闭包,所谓闭包,就是那些可以使用非全局变量、非自身的变量(称为自由变量)的函数,或者说延展了自身作用域的函数。只有涉及嵌套定义的函数,才会涉及到闭包。例如一个函数1内部定义了函数2,函数2可以使用函数1的变量,那么函数2和它所引用的自由变量就构成了一个闭包。典型例子:
# series为了保存历史值,这是很烂的写法 def make_averager(): series = [] def average(n): series.append(n) return sum(series) / len(series) # 改进的写法 def make_averager(): total = 0 sum = 0 def average(n): nonlocal total, sum total +=1 sum += n return sum / total
仔细一看,这跟装饰器很像。
装饰器是闭包的应用。装饰器的参数是被修饰的函数,在执行完额外工作后,要返回被装饰函数或另一个函数。
简单的装饰器的写法:
# 装饰器定义 def d1(func): print("decorator_1") return func # 放置装饰器 @d1 def func(n) print(n)
解释:上面的代码中,执行func()这个函数等价于 func = d1( func),也就是说,执行代码时,func是被d1装饰的结果的引用,不是原始的func。f = func(100),print(f)和print(func)结果不一样。装饰器另外一个关键特性是:在加载模块时,他们在被装饰的函数定义之后立即执行,可见下面的代码:
def decorator_1(func): def miao(func): print("fck you, return func") return func print("decorator_1, return miao") return miao def decorator_2(func): print("decorator_2, return func") return func #叠放装饰器,结果等价于d1( d2( func_1)) @decorator_1 @decorator_2 def func_1(val): print(val) if __name__ == "__main__": print("Main()") func_1(1000)
执行结果为:
decorator_2, return func decorator_1, return miao Main() fyou, return func
这说明d2,d1两个装饰器在真正的被装饰函数func调用前,被执行。假如d2装饰了n个函数,那么d2要先执行n次。
参数化装饰器
参数化装饰器一般是有几层嵌套,其中某一层及其以内是装饰器,外面的是包装着装饰器的工厂函数,工厂函数可以接受外界传给它的其他参数。
看看例子:
# 《Fluent Python》page 165 example-7-15 import time def clock(func): def clocked(*args): # ?? # extra .. result = func(*args) return result return clocked
那么这里就有问题了:
Q1:*args是谁的参数?
从上下文中可知*args应该是func接受的函数,但是在上文装饰器的定义中,没有显式看到*args。由此推测:装饰器中的*args, **kwargs这些容器分别存放了被修饰函数func的positional args 和 keyword args。
Q2:如果改为decorator_1(func, *args, **kwargs),那么这两个容器放的是谁的参数?
ans:经过尝试,发现会抛出异常。如果写:
- @decorator_1(**dict1), decorator_1()missing a required positional arg 'func'.
- @decorator_1(kwargs=dict1) , decorator_1()missing a required positional arg 'func'.
- @decorator_1(func=func_1, kwargs=dict1), name 'func_1' is not defined.
通过查看他人代码实例,发现每个装饰器及其内部的函数,都有一个函数只有func一个参数。在《Fluent Python》page 76 example-7-25的注释中,第二条注释写道:decorate(func)是真正的函数。第一条注释:clock(...)是装饰器工厂函数。由此可以推测,在Q2中,d1这个入口不是真正的装饰器,真正装饰器是miao(),参数只有一个func。
下面的代码能够说明这一情况:
dict1 = { "name":"miao", "type":"zhizhi" } dict2 = { "name":"guagua", } def decorator_1(**kwargs): def miao(func): def zhi(**kwargs): for k,v in kwargs.items(): # 打印dict1 print(k, v) return func return zhi for k,v in kwargs.items(): # 打印dict2 print(k, v) print("decorator_1, return miao") return miao @decorator_1(**dict2) def func_1(**kwargs): print("f") if __name__ == "__main__": func_1(**dict1)
d1接受的是dict2, func_1的参数是dict1, 运行结果是先打印dict2的内容,再打印dict1的内容。也就是说:miao(func)是真正的装饰器,其内部函数使用的**kwargs, *args都是被装饰函数的参数;作为工厂函数的d1,它接受的参数是外部直接传给的,区别于func的参数。参数化装饰器的另一个特点是,要写作 @decorator(),因为它可以接受参数。
装饰器的应用
- 单分派,多分派函数,把多个函数捆绑组成一个泛函数。
- 标准库的装饰器
- functools.wraps
- functools.lru_cache,参数化装饰器,做缓存工作,提高函数的速度,但被其修饰的函数所有参数必须可散列。
- functools.singledispatch( Py 3.4+)