zoukankan      html  css  js  c++  java
  • 装饰器之技术小总结

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

    ##### 愿你一寸一寸地攻城略地,一点一点地焕然一新 #####
  • 相关阅读:
    javascript脚本轻松实现局部刷新
    asp.net中web.config 文件使用一则
    javascript脚本轻松实现局部刷新
    无限级树,ajax+asp.net2.0+Sql实现无限树
    能连接4种数据库(外加文件操作)的DatabaseHelper类
    Sql Server 存储过程分页
    FreeBSD iscsi 安装配置
    win7访问共享文件夹提示“未知的用户名或密码错误”
    Adobe CS5安装失败解决办法
    删除windows里保存的访问网络资源的帐号密码
  • 原文地址:https://www.cnblogs.com/johnyang/p/10494413.html
Copyright © 2011-2022 走看看