装饰器的作用是在已有的可调对象(callable object)的基础上,插入代码,以增强或者管理可调对象,装饰器的核心就是通过传入一个可调对象,然后返回一个可调对象,就其装饰的对象而言,可以分为函数装饰器和类装饰器,就其构造方法而言,可以用嵌套函数(nested functions)或者类方法。
下面通过几个小栗子总结装饰器的技术要点:
(1)@decorator的含义及其等效手动操作
@decorator实际上是自动的完成了将被装饰对象的接口传入装饰器,并返回一个可调对象。
>>> def decorator1(F): def wrapper(*args): print('i am in wrapper') F() print('it is doing @ operation....') return wrapper
>>> @decorator1 def F(): print('that is F() function') it is doing @ operation.... >>>
实际上,@操作的等效手动操作为:
>>> F=decorator1(F) it is doing @ operation....
所以我们可以通过这种手动等效操作来理解装饰器的设计结构:当出现@decoration时,把下面被装饰的接口作为参数传入decoration中,并形成一个可调对象,所以decoration的参数为F(不一定非与被装饰的接口的名字一样),然后返回wrapper可调对象。而当对F调用时,实际调用的是decorator1(F),即F()实际上是decorator1(F)(),即wrapper(*args).
调用F()
>>> F() i am in wrapper that is F() function
(2)基于类方法的装饰器
上栗中实际上是基于嵌套函数的装饰器,不再赘述,再总结下基于类的装饰器。类方法构造装饰器也要满足装饰器的基本功能:接收可调对象接口,返回可调对象。如果是类方法的话,对于这个要求,接受可调对象接口可以由__init__完成,返回可调对象,则应在类里定义__call__方法。下面用基于类方法来构造(1)中的构造器。
>>> class decorator1: def __init__(self,F): self.F=F print('it is doing @ operation...') def __call__(self,*args): print('i am in wrapper') self.F(*args) >>> @decorator1 def F(): print('that is F() function') it is doing @ operation...
调用F()
>>> F() i am in wrapper that is F() function
(3)多重装饰器
实际中,有可能对一个函数进行多重装饰,多重装饰的语法即直接在被装饰对象上面层层叠加@decoration。
>>> def dec1(F): def wrapper(*args): print('level 1') F() print('dec1') return wrapper >>> def dec2(F): def wrapper(*args): print('level 2') F() print('dec2') return wrapper >>> def dec3(F): def wrapper(*args): print('level 3') F() print('dec3') return wrapper >>> @dec1 @dec2 @dec3 def F(): print('F()') dec3 dec2 dec1 >>> F() level 1 level 2 level 3 F()
>>> def A(): print('A()') >>> A=dec1(dec2(dec3(A))) dec3 dec2 dec1 >>> A() level 1 level 2 level 3 A()
上面的例子给出了多重装饰器的等效手动方法,以及多重装饰器在传入被装饰对象接口参数的顺序(多重装饰器从下往上,手动方法的由内而外),调用被装饰对象时wrapper函数的调用顺序(多重装饰器从上往下,手动方法的由外而内)
(4)几个注意的地方
(a).为什么装饰器是嵌套函数形式或者类方法的__call__函数?
>>> def decorator(func):
def wrapper(*args,**kwargs):
func(*args,**kwargs)
return wrapper
以上述嵌套函数代码为典型,进行讨论:对于函数装饰器而言,如果是修饰简单函数(或者非绑定方法),在调用时格式为func(*args,**kwargs),可以看作分为两步,第一步:形成func,这里,func实际上是decorator(func),而func为可调对象(callable objects),所以decorator(func)也必须是可调对象,即decorator函数要返回一个可调对象,即return wrapper,这里的wrapper即必须为可调对象,而wrapper不能写到return 下方,所以wrapper必为内层函数,而又要可调,故也可以设计为一个函数,所以实际的func就是wrapper;第二步:接受参数,所以这里wrapper要接受来自func(*args,**kwargs)的参数,而又要要求输出本来的函数的结果,所以必须要有func(*args,**kwargs)。如果是修饰绑定方法,即装饰类方法,嵌套函数式装饰器仍然可以正常工作,但基于类方法的函数装饰器,就要必须结合描述符,否则会因为在传参过程中,缺少被修饰函数实例参数而失败。如下:
>>> class decorator: def __init__(self,func): self.func=func def __call__(self,*args,**kwargs): self.func(*args,**kwargs)
>>> class a: def __init__(self): pass @decorator def b(self): pass >>> x=a() >>> x.b() Traceback (most recent call last): File "<pyshell#40>", line 1, in <module> x.b() File "<pyshell#30>", line 5, in __call__ self.func(*args,**kwargs) TypeError: b() missing 1 required positional argument: 'self'
如上代码,提示在传参时候,缺少了被装饰类的实例参数self,而加入__get__方法,使之成为描述符,即使被修饰方法称为类的虚拟属性,就可以获取到被修饰类实例参数。下面就这一思想进行改造:
class decorator: def __init__(self,func): self.func=func def __call__(self,*args,**kwargs): self.func(*args,**kwargs) def __get__(self,instance,owner): def wrapper(*args,**kwargs): return self(instance,*args,**kwargs) return wrapper
>>> class a: def __init__(self): pass @decorator def b(self): print('ok!') >>> x=a() >>> x.b() ok!
这里,还可以通过两步法进行分析,因为进行调用实例方法时即x.b(),第一步x.b获取一个绑定函数对象,而x.b会触发类a的__get__方法,所以__get__必须返回一个可调对象,即wrapper,并且在第二步传参时,wrapper可以获取参数,但在调用时,可以手动的将获取到的Instance传入self.func中,或则直接用可以触发__call__的self(instance,*args,**kwargs),而对于简单函数,则忽略__get__,直接调用__call__。