美文网首页程序员
python之装饰器

python之装饰器

作者: 我只是我笔下的小丑 | 来源:发表于2018-12-14 18:04 被阅读4次

    1. 什么是装饰器

    知乎大佬如是说:
    内裤可以用来遮羞,但是到了冬天它没法为我们防风御寒,聪明的人们发明了长裤,有了长裤后宝宝再也不冷了,装饰器就像我们这里说的长裤,在不影响内裤作用的前提下,给我们的身子提供了保暖的功效。
    装饰器本质上是Python函数,可以为已存在的对象添加额外的功能,同时装饰器还可以抽离出与函数无关的重用代码。具体应用场景如:插入日志、性能测试、事务处理、缓存、权限校验等。

    换言之

    装饰器不能影响原函数的功能,装饰器是独立出来的函数。谁调用它,谁就可以使用它的功能。

    2.举个栗子

    add的功能是计算x和y的值,我们称作功能函数。
    logger的作业是在执行add函数的同时再打印了其他的信息,这部分的作为add的功能增强,我们称为装饰。
    在logger里我们可以加入其他类似的功能函数,也能包装它,可以进行复用。

    1.引子

    #功能函数
    def add(x,y):
        return x+y
    
    #装饰函数
    def logger(fn):
        print('frist')
        x = fn(4,5)
        print('second')
        return x 
    
    print(logger(add))
    
    #把函数add传给logger  ,return x+y
    #print('frist')
    #print('secend')
    #  x = fn(4,5)  ==> x = 4 y= 5  x= 4+5 = 9 
    #return 9 
    
    
    frist
    second
    9
    

    2.提取参数

    x,y的参数都放在logger函数内部了,影响函数的灵活性,此处我们可以提取出来。

    def add(x,y):
        return x + y
     
    def logger(fn,*args,**kwargs):
        print('frist')
        x = fn(*args,**kwargs)
        print('second')
        return x
    
    print(logger(add,1,y=11))
    
    frist
    second
    12
    

    3.柯里化

    def add(x,y):
        return x + y
    def logger(fn):
        def wrapper(*args,**kwargs):
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        return wrapper
    
    
    print(logger(add)(5,y=11))
    
    begin
    end
    16
    

    懵逼ing

    以下为个人理解,左边为非柯里化函数,右边是柯里化函数。

    <img src="http://qiniu.mykernel.cn/klh.png" alt="柯里化函数"/>

    前面说过柯里化的定义,本来可以一次传入两个参数,柯里化之后。只需要传入一个函数了。。
    左边传入add 和 两个参数。
    右边的logger(add)是一个函数,只需要传入两个参数。logger(add)是个整体,结合成一个函数。当然这样写,我们看函数主题的部分也是不一样的。
    函数的基础中说过,函数的传参必须和函数参数的定义一致。重点分析右边函数(柯里化)。
    参数部分:参数传入的方式,logger函数需要传入个fn,fu的返回值是wrapper函数,wrapper函数的参数是(*args,**kwargs)所以此次就需要分两次传入参数。
    第一次传入fn,再次传入wrapper函数需要的参数。所以就出现了最下边的调用方式。
    print(logger(add)(5,y=50))。

    返回值部分:右侧的logger函数是个嵌套函数,logger的返回值是wrapper,内层的wrapper函数返回值是x,x = fn(*args,**kwargs)。fn函数是最后调用时候传入的add函数。

    懵逼 X 2。。。。

    def add(x,y):
        return x + y
      
    def logger(fn,*args,**kwargs):        def logger(fn):  #参数剥离
                                               def newfunction(*args,**kwargs):  #新定义一个函数,logger函数返回也是这个函数名字
        print('frist')                           print('frist')
        x = fn(*args,**kwargs)  == >             x = fn(*args,**kwargs)
        print('second')                          print('second')
        return x                               return x
                                            return newfunction
                                                                  
    print(logger(add,1,y=11))           print(logger(add)(5,y=11)) #两次传入参数
    

    效果如下:

    def add(x,y):
        return x + y
      
    def logger(fn):  #参数剥离
        def newfunction(*args,**kwargs):  #新定义一个函数,logger函数返回也是这个函数名字
       
            print('frist')
            x = fn(*args,**kwargs)
            print('second')
            return x
        
        return newfunction
                                                                  
    print(logger(add)(5,y=11)) #两次传入参数
    
    frist
    second
    16
    

    继续懵逼的话就这样用吧。。。用多了就悟道了。。

    4.装饰器语法糖

    #再次变形。。。
    def add(x,y):
        return x + y
    
    def logger(fn):
        def wrapper(*args,**kwargs):
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        return wrapper
    
    ##调用方法1:
    print(logger(add)(x=1111,y=1))
    
    ##调用方法2:
    add = logger(add)
    print(add(x=11,y=3))
    
    ##调用方法3: python给我们的语法糖 
    
    @logger # 说明下边的函数,add 其实是 add = logger(add)
    def add(x,y):
        return x + y
    
    
    print(add(45,40))
    
    begin
    end
    1112
    begin
    end
    14
    begin
    end
    85
    

    3.复杂的栗子

    import datetime
    import time 
    
    def logger(fn):
        def warp(*arges,**kwarges):
            print("arges={},kwarges={}".format(arges,kwarges))  #打印函数的两个参数
            start = datetime.datetime.now()  #获取函数运行的开始时间
            ret = fn(*arges,**kwarges)  #传入两个参数,调用add函数  此处有个return的值,需要一层一层的返回出去
            
            duratime = datetime.datetime.now() - start  #获得函数的运行时间
            print("function {} took {}s".format(fn.__name__,duratime.total_seconds()))  #打印函数的运行时间
    
            return ret   #返回fn的结果 ,fn = x+y ==> 返回x+y的值。  x = 4 y= 11 ==> return 11
        return warp  #返回warp的 return ==> ret 的return ==> return 11 函数的最终结果为11  
    
    @logger
    def add(x,y):
        print("oooooook")
        time.sleep(1.5)
        return x+y
    
    print(add(4,y=11))
       
    #如果充分理解了每个小部件,这个简单的完整版本也是很好理解的了。
    #1,logger是个装饰器,而且使用了柯里化技术
    #2,add 传参给logger的fn 形参,add(4,y=5)的两个参数传入给warp函数的两个形参
    #
    #
    
    arges=(4,),kwarges={'y': 11}
    oooooook
    function add took 1.5017s
    15
    

    再次翻译

    import datetime
    import time 
    
    #####################################装饰开始############################################
    def logger(fn):  #拿到函数名称
        def warp(*arges,**kwarges):  #拿到函数带过来的参数开始装饰
            print("arges={},kwarges={}".format(arges,kwarges))   #来试试打印两个参数
            start = datetime.datetime.now()  #
            ret = fn(*arges,**kwarges)   # 此处调用add函数。开始执行函数,发现return语句。。ret的结果就是return。 
            
            duratime = datetime.datetime.now() - start  #
            print("function {} took {}s".format(fn.__name__,duratime.total_seconds()))
    
            return ret   #加工完成开始返回。warp的返回值是ret ,ret的返回值是 add函数的执行结果(原函数的功能完整的保留了) 
        return warp  # logger的返回结果是warp,warp的返回值是ret ,ret的返回值是 add函数的执行结果(原函数的功能完整的保留了) 
    
    #####################################装饰完成############################################
    
    @logger  #装饰工厂
    ######add是需要被装饰的函数,当你有这个想法的事情,其实事情已经开始发生了。
    def add(x,y): # 此时add = logger(add)  此处前面的@logger标记就是想要让logger装饰器像一个工厂一样对add函数进行加工。
        print("oooooook")
        time.sleep(1.5)
        return x+y
    
    print(add(4,y=11))
    
    
    arges=(4,),kwarges={'y': 11}
    oooooook
    function add took 1.501604s
    15
    

    4.带参装饰器

    1. 文档字符串

    我们约定,在python函数的第一行需要对函数进行说明,使用三引号表示。
    如果是英文说明,惯例首字母大写,第一行写概述,空一行,第三行写详细描述。
    如果函数中有文档字符串,默认会放在函数的doc属性中,可以直接访问。

    def add(x,y):
        """This is a function of addition"""
        a = x+y
        return x + y
    
    print("function name is {}\nfunction doc = {}\n\n".format(add.__name__, add.__doc__))
    print(help(add))
    
    function name is add
    function doc = This is a function of addition
    
    
    Help on function add in module __main__:
    
    add(x, y)
        This is a function of addition
    
    None
    

    2. 前面装饰器的副作用

    前面装饰器基本上已经可以完成对函数进行加强的功能了,但是还有些瑕疵。比如原来函数的原属性已经被替换为装饰器的属性了。如下:

    def add(x,y):
        return x + y
    
    def logger(fn):
        "This is logger doc"
        def wrapper(*args,**kwargs):
            "This is wrapper doc"
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        return wrapper
    
    
    @logger # add = logger(add)
    def add(x,y):
        "This is add doc "
        print("name = {}\ndoc = {}".format(add.__name__,add.__doc__))
        return x + y
    
    
    print(add(45,40))
    
    #可以看出来add被装饰出来的函数(新的add)的属性已经全部改变了。
    
    begin
    name = wrapper
    doc = This is wrapper doc
    end
    85
    

    3. 解决方案一

    三个函数:

    1. copy原函数的属性 copy_properties
    2. 装饰器 logger
    3. 功能函数 add
    def copy_properties(src, dst): # 把src的相关属性赋值给dst  (fn,wrap)
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
    
        
    def logger(fn):
        """'This is a function of logger'"""
        def wrap(*arges,**kwarges): # 
            """'This is a function of wrap'"""
            print('<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>')
            x = fn(*arges,**kwarges)
            #print("name={}\ndoc={}".format(add.__name__,add.__doc__))
            print('<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>')   
            return x  
        copy_properties(fn,wrap)    #思考1:为什么放在这个位置调用
        return wrap
    
    @logger
    def add(x,y):
            """'This is a function of add'"""
            print("name={}\ndoc={}".format(add.__name__,add.__doc__))
            return x+y
    
    
    print(add(4,6))
    
    <>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>
    name=add
    doc='This is a function of add'
    <>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>
    10
    

    4. 解决方案二

    但凡使用装饰器都会出现属性的这个问题,为什么不把copy_properties也做成装饰器呢?

    三个函数:

    1. copy原函数的装饰器 copy_properties1
    2. 装饰器 logger
    3. 功能函数 add
    def copy_properties(src, dst): # 把src的相关属性赋值给dst  (fn,wrap)
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        
    #利用前面的知识我们可以对copy_properties轻松进行变形
    def copy_properties1(src): #  把src的相关属性赋值给dst  (fn,wrap)  
        def _copy(dst):
            dst.__name__ = src.__name__
            dst.__doc__ = src.__doc__
            return dst 
        return _copy
    

    带参装饰器:

    def logger(fn): 
        """'This is a function of logger'"""
        @copy_properties1(fn) #wrap = copy_properties(fn)(wrap)  
        #== > 柯里化 两次传入参数 src = fn , dst = wrap 新的wrap函数的属性已经替换为原函数的。
        
        def wrap(*arges,**kwarges): #wrap = copy_properties(fn)(wrap)(*arges,**kwarges)     
            """'This is a function of wrap'"""
            print('>->->->->->->->->->->->->->->->->->->->->->->->->->')
            x = fn(*arges,**kwarges)
            print('<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<')
            return x 
    
        return wrap
    
    @logger  #add =logger(add)
    def add(x,y):
            """'This is a function of add'"""
            print("name={}\ndoc={}".format(add.__name__,add.__doc__))
            return x+y
    
    
    
    print(add(4,11))
    
    >->->->->->->->->->->->->->->->->->->->->->->->->->
    name=add
    doc='This is a function of add'
    <-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<
    15
    

    更多欢迎访问:http://www.mykernel.cn/

    相关文章

      网友评论

        本文标题:python之装饰器

        本文链接:https://www.haomeiwen.com/subject/slzghqtx.html