• Python装饰器


    装饰器(无参)本质上它就是一个函数,同时函数作为它的形参,它的返回值也是一个参数,它可是使用@functionname方式,来简化调用!装饰器本质上来说,它就是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强),所以叫装饰器。

    比如现在有这样的一个需求,一个加法函数,现在想要增强它的功能,能够输出被调用过以及调用的参数信息。

    def add(a,b):
        return a+b

    要对这个函数增强,要是没有装饰器,会像下面这样来修改。

    def add(a,b):
        print("call add,a+b")#日志输出到控制台
        return a+b

    上面的函数虽然完成了需求,但是它有弊端,第一是打印语句的耦合太高,第二是加法函数属于业务功能,而输出信息的功能则属于非业务功能代码,不应该放在业务函数的加法中。所以可以向下面的写法进化:

    def add(a,b):
        return a+b
    
    def logger(fn):
        print("begin")#增强的输出
        x = fn(4,5)
        print("end")#增强的功能
        return x
    
    print(logger(add))
    
    结果为:
    begin
    end
    9

    上面的代码虽然做了业务分离,但是fn函数的传参却又是个问题。因为函数把参数写死了。可以向下面这样修改:

    def add(x,y):
        return x + y
    
    def logger(fn,*args,**kwargs):
        print('begin')
        x = fn(*args,**kwargs)
        print('end')
        return x
    
    print(logger(add,5,y=60))
    
    结果为:
    65

    然后将上面的这个函数进行柯里化。就得到了下面的变形。

    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=50))
    
    结果为:
    55

    而将 print(logger(add)(5,y=50))再换一种写法,add=logger(add),print(add(x=5,y=10))而这可以简化为下面的,也就是装饰器的写法:

    #装饰器
    def logger(fn):
        def wrapper(*args,**kwargs):
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        return wrapper
    
    @logger # 等价于add = logger(add)
    def add(x,y):
        return x + y
    
    print(add(45,40))
    
    结果为:
    begin
    end
    85

    上面代码的@logger就是装饰器语法。

    import datetime
    import time
    def logger(fn):
        def wrap(*args, **kwargs):
            # before 功能增强
            print("args={}, kwargs={}".format(args,kwargs))
            start = datetime.datetime.now()
            ret = fn(*args, **kwargs)
            # after 功能增强
            duration = datetime.datetime.now() - start
            print("function {} took {}s.".format(fn.__name__, duration.total_seconds()))
            return ret
        return wrap
    
    @logger # 相当于 add = logger(add)
    def add(x, y):
        print("===call add===========")
        time.sleep(2)
        return x + y
    
    print(add(4, y=7))
    
    结果为:
    args=(4,), kwargs={'y': 7}
    ===call add===========
    function add took 2.009831s.
    11

    应该怎么理解装饰器?

    其实装饰器就是一个函数,也就是给画装一个画框, 画框可以经常换,避免切入式的修改代码。如下图:

    文档字符串(documention strings)

    函数文档字符串也就是在函数语句块的第一行(第二行是不行的),且习惯是多行的文本,所以多使用三引号惯例是首字母大写,第一行写概述,空一行,第三行写详细描述, 可以使用特殊属性__doc__访问这个文档。

    def add(x,y):
        """This is a function of addition
        
        加法函数
        """
        a = x+y
        return x + y
    
    print("name={}
    doc={}".format(add.__name__, add.__doc__))
    help(add)
    
    结果为:
    name=add
    doc=This is a function of addition
        
        加法函数
        
    
    help(add)#调的也就是doc
    
    结果为:
    Help on function add in module __main__:
    
    add(x, y)
        This is a function of addition
        
        加法函数

    装饰器也有副作用,比如下面的代码:

    def logger(fn):
        def wrapper(*args,**kwargs):
            'I am wrapper'
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        return wrapper
    
    @logger #add = logger(add)
    def add(x,y):
        '''This is a function for add'''
        return x + y
    
    print("name={}, doc={}".format(add.__name__, add.__doc__))
    
    结果为:
    
    name=wrapper, doc=I am wrapper

    由上面的例子可以看出,原函数对象的属性都被替换了,而使用装饰器,我们的需求是查看被封装函数的属性,也就是查看帮助文档的时候,帮助文档也效能相应的改变了。这个时候应该怎么办?这个时候可以提供一个函数,然后被封装函数属性==copy==》包装函数属性。

    def copy_properties(src, dst): # 可以改造成装饰器
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
    
    def logger(fn):
        def wrapper(*args,**kwargs):
            'I am wrapper'
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        copy_properties(fn, wrapper)
        return wrapper
    
    @logger #add = logger(add)
    def add(x,y):
        '''This is a function for add'''
        return x + y
    
    print("name={}, doc={}".format(add.__name__, add.__doc__))
    
    结果为:
    name=add, doc=This is a function for add
     
    def copy_properties(src, dst): # 可以改造成装饰器
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        dst.__qualname__ = src.__qualname__
    
    def logger(fn):
        def wrapper(*args,**kwargs):
            'I am wrapper'
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        
        copy_properties(fn, wrapper)
        return wrapper
    
    @logger #add = logger(add)
    def add(x,y):
        '''This is a function for add'''
        return x + y
    
    print("name={}, doc={},qualnane={}".format(add.__name__, add.__doc__,add.__qualname__))
    
    结果为:
    
    name=add, doc=This is a function for add,qualnane=add

    上面的函数可以通过copy_properties函数将被包装函数的属性覆盖掉包装函数,凡是被装饰的函数都需要复制这些属性,这个函数很通用,同时可以将复制属性的函数构建成装饰器函数,带参装饰器。也就是改造成一个带参装饰器。

    def copy_properties(src): # 柯里化
        def _copy(dst):
            dst.__name__ = src.__name__
            dst.__doc__ = src.__doc__
            return dst
        return _copy
    
    def logger(fn):
        @copy_properties(fn) # @_copy==》wrapper = copy_properties(fn)(wrapper)
        def wrapper(*args,**kwargs):
            'I am wrapper'
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        return wrapper
    
    @logger #add = logger(add)
    def add(x,y):
        '''This is a function for add'''
        return x + y
    
    print("name={}, doc={}".format(add.__name__, add.__doc__))
    
    结果为:
    
    name=add, doc=This is a function for add

    现在又有一个需求,需要获取函数的执行时长,对时长超过阈值的函数记录一下。

    def logger(duration):
        def _logger(fn):
            @copy_properties(fn) # wrapper = wrapper(fn)(wrapper)
            def wrapper(*args,**kwargs):
                start = datetime.datetime.now()
                ret = fn(*args,**kwargs)
                delta = (datetime.datetime.now() - start).total_seconds()
                print('so slow') if delta > duration else print('so fast')
                return ret
            return wrapper
        return _logger
    
    @logger(5) # add = logger(5)(add)
    def add(x,y):
        time.sleep(3)
        return x + y
    
    print(add(5, 6))
    
    结果为:
    so fast
    11

    上面的函数就是一个带参装饰器,带参装饰器也是一个函数,函数作为它的形参,而返回值是一个不带参的装饰器函数,使用@functionname(参数列表)方式调用,可以看着在装饰器外层又加了一层函数。

    将记录的功能提取出来,这样就可以通过外部提供的函数来灵活的控制输出。

    def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))):
        def _logger(fn):
            @copy_properties(fn) # wrapper = wrapper(fn)(wrapper)
            def wrapper(*args,**kwargs):
                start = datetime.datetime.now()
                ret = fn(*args,**kwargs)
                delta = (datetime.datetime.now() - start).total_seconds()
                if delta > duration:
                    func(fn.__name__, duration)
                return ret
            return wrapper
        return _logger

    上面的也还是一个带参装饰器。

    functools模块

    functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES),这个函数类似于copy_properties功能, wrapper 包装函数、被更新者。wrapped 被包装函数、数据源。元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性,'__module__', '__name__', '__qualname__', '__doc__', '__annotations__',模块名、名称、限定名、文档、参数注解。元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典,增加一个__wrapped__属性,保留着wrapped函数。

    import datetime, time, functools
    def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))):
        def _logger(fn):
            def wrapper(*args,**kwargs):
                start = datetime.datetime.now()
                ret = fn(*args,**kwargs)
                delta = (datetime.datetime.now() - start).total_seconds()
                if delta > duration:
                    func(fn.__name__, duration)
                return ret
            return functools.update_wrapper(wrapper, fn)
        return _logger
    
    @logger(5) # add = logger(5)(add)
    def add(x,y):
        time.sleep(1)
        return x + y
    
    print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='
    ')
    
    结果为:
    11
    add
    <function add at 0x0478C390>
    {'__wrapped__': <function add at 0x0478C390>}
    import datetime, time, functools
    def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))):
        def _logger(fn):
            @functools.wraps(fn)
            def wrapper(*args,**kwargs):
                start = datetime.datetime.now()
                ret = fn(*args,**kwargs)
                delta = (datetime.datetime.now() - start).total_seconds()
                if delta > duration:
                    func(fn.__name__, duration)
                return ret
            return wrapper
        return _logger
    
    @logger(5) # add = logger(5)(add)
    def add(x,y):
        time.sleep(1)
        return x + y
    
    print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='
    ')
    
    结果为:
    
    11
    add
    <function add at 0x0478CDF8>
    {'__wrapped__': <function add at 0x0478CDF8>}
  • 相关阅读:
    sublime使用及插件
    Unity 查找
    Unity 3D 的四种坐标系
    C#知识点<4>
    C#知识点<3>
    C#知识点<2>
    排序算法
    OOP的三大特性------封装、继承、多态
    C#常用函数
    C++-------------类和对象
  • 原文地址:https://www.cnblogs.com/xpc51/p/11710380.html
Copyright © 2020-2023  润新知