装饰器之技术小总结

装饰器的作用是在已有的可调对象(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__。

 

posted @ 2019-03-08 11:37  JohnYang819  阅读(234)  评论(0编辑  收藏  举报