• chapter5.2装饰器


    装饰器

    有一个需求,将一个加法函数,增加一个功能,输出调用的参数,及调用信息

    在源代码里插入代码,叫硬编码,不利于更改。非业务功能输出信息,不应该放在业务代码里。

    def add(x,y):
        """ function add """
        return x+y
    
    def logger(fn,*args,**kwargs):
        print('sdfasd')
        ret = fn(*args,**kwargs)
        return ret
    print(logger(add,4,5))

    定义两个函数,调用后加强输出,但是函数传参是个问题,使用以下方法,*args和**kwargs

    将函数柯里化

    def logger(fn):
        def wrapper(*args,**kwargs):
            print('Function is {}'.format(wrapper.__name__))
            ret = fn(*args,**kwargs)
            return ret
        return wrapper
    def add(x,y):
      """ function add """
      return x+y
    print(logger(add,4,5))

    柯里化是为了带参装饰器的应用,后边有。

    装饰器语法糖

    def logger(fn):
        def wrapper(*args, **kwargs):
            print('Function is {}'.format(fn.__name__))
            ret = fn(*args, **kwargs)
            return ret
        return wrapper
    
    @logger  ##==>add = logger(add)
    def add(x, y):
        """ function add """
        return x + y
    
    print(add(4, 4))###返回"Function is add"

    文档字符串

    Python的文档字符串Documentation Strings

    在函数或者类的语句块下的第一行,一般使用三引号,因为文本大多是多行的

    惯例是首字母大写,第一行概述,空一行,第三行写详细描述,

    可以使用类或者类对象的__doc__访问该文档

    装饰器(无参)

    装饰器可以是函数,也可以是类,只要可调用就可以使用,返回值是一个函数或对象,使用@装饰器name,魔术方式,装饰器就是高阶函数,但装饰器是对传入函数的功能的增强(装饰)

    add = logger(add)

    这句中函数被重新覆盖,但是原来的add指向的地址被logger中的fn引用,仍然存在。这里fn使用了闭包

     装饰器的副作用:

    def logger(fn):
        def wrapper(*args, **kwargs):
            '''This is a warp'''
            print('function is {}'.format(fn.__name__))##add
            print('doc: {}'.format(fn.__doc__))##add的文档
            ret = fn(*args, **kwargs)
            return ret
        return wrapper
    
    @logger
    def add(x, y):
        '''This is a function of addition'''
        return x + y
    
    ret = add(4, 5)
    print(ret, add.__doc__)##这里返回的文档是warp的,add本身的属性并没有显示

    这里可以调用fn.__doc__查看文档属性,想要看原来add的文件属性,在全局只能看到logger的,add函数的文档只用原来的地址,现在被装饰器的wrapper的fn记录,因为函数内部的变量外部不可见,  

    带参装饰器

    python中提供有相应的函数,要是自己实现,可以使用两层装饰器。外层可以使用带参函数

    def copy_properties(src):##源函数
        def copy_inner(dst):##目标函数
            dst.__name__ = src.__name__
            dst.__doc__ = src.__doc__
            return dst
        return copy_inner
    
    def logger(fn):
        @copy_properties(fn)  ####wrap = copy(fn)(wrap)    代参装饰器
        def wrapper(*args, **kwargs):
            '''This is a warp'''
            print('function is {}'.format(fn.__name__))
            print('doc: {}'.format(fn.__doc__))
            ret = fn(*args, **kwargs)
            return ret
        return wrapper
    
    @logger  ###add = logger(add)
    def add(x, y):
        '''This is a function of addition'''
        return x + y
    
    ret = add(4, 5)
    print(ret, add.__doc__)

    注意装饰器的等价形式,带参装饰器的变换,将函数柯里化。

    以上函数,copy函数修饰了logger,将函数add的属性复制到了函数warp上,只复制了名字和文档的属性,要想全部覆盖,可以使用wrapper函数

    带参装饰器练习

    要求获取函数的执行时常,对时常超过阈值的函数记录一下

    import datetime,time,functools
    def
    copy_properties(source): def copy_inner(destin): destin.__name__ = source.__name__ destin.__doc__ = source.__doc__ return destin return copy_inner def logger(duration): def logger(fn): @copy_properties(fn) ##wrapper = copy_properties(fn)(wrapper) def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now()-start).total_seconds() print('time out') if delta > duration else print('time enought quick') ##函数超时的模拟使用time.sleep方法,对超时的操作也可以提出 return ret return wrapper return logger @logger(3) ##add = logger(4)(add) def add(x,y): time.sleep(3) return x+y print(add(3,2))

    将上个程序的logger函数改成以下的函数,就可以灵活的控制得到的结果的处理方式。


    def
    logger(duration,func=lambda name,duration:print('{} took {}'.format(name,duration))): def logger(fn): @copy_properties(fn) ##wrapper = copy_properties(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__,delta) return ret return wrapper return logger

    带参装饰器是一个函数,函数作为形参,返回值是不带参数的装饰器函数,使用@函数名(参数列表)方式调用,可以看作装饰器外层又加了一层函数

    functools类下的wraps方法

    from functools import wraps 调用方法

    import functools
    import datetime
    import time
    
    def logger(duration,func=lambda name,duration:print('{} took {}'.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__,delta)
                return ret
            return functools.update_wrapper(wrapper,fn)##更新文档
        return logger
    
    @logger(3)  ##add = logger(4)(add)
    def add(x,y):
        time.sleep(3)
        return x+y
    print(add(3,2))

    functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=('__dict__',))

      类似于copy_properties功能

      wrapper包装函数,被更新者,

      wrapper被包装函数,数据源

      元组WRAPPER__ASSIGNMENTS中是要覆盖的属性

        ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')

        模块名,名称,限定名,文档,参数注解

      元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典,更新属性字典

      增加一个__wrapper__属性,保留wrapped的属性

    import functools
    import datetime
    import time
    
    def logger(duration,func=lambda name,duration:print('{} took {}'.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__,delta)
                return ret
            return functools.update_wrapper(wrapper,fn)
        return logger
    
    @logger(3)  ##add = logger(4)(add)
    def add(x,y):
        time.sleep(3)
        return x+y
    print(add(3,2))

    装饰器使用,@wraps,装饰器方法

    @functools.wraps( wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

    wrapped 被包装函数

     

    以下为类方法解决,没看到类请跳过。

    装饰器还可以通过上下文管理实现。__enter__和__exit__

    import time,datetime
    from functools import update_wrapper
    
    class TimeIt:
        '''This is wtapper function'''
        def __init__(self,fn):
            self.fn = fn
            update_wrapper(self,fn)##更新函数文档
    
        def __enter__(self):
            self.start = datetime.datetime.now()
            return self.fn
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print((datetime.datetime.now()-self.start).total_seconds())
    
    def add(x,y):
        '''This is add function'''
        time.sleep(1)
        return x+y
    
    with TimeIt(add) as fn:##TimeIt(add)是TimeIt的实例对象,
        print(fn(4,5))

    还可以通过创建实例对象,然后调用它实现,用到了__call__方法

    import time,datetime
    from functools import update_wrapper
    class TimeIt:
        '''This is wtapper function'''
        def __init__(self,fn):
            self.fn = fn
            update_wrapper(self,fn)
    
        def __call__(self, *args, **kwargs):###实例可调用
            start = datetime.datetime.now()
            ret = self.fn(*args, *kwargs)
            print((datetime.datetime.now() - start).total_seconds())
            return ret
    
    def add (x,y):
        '''This is add function'''
        time.sleep(1)
        return x+y
    
    a = TimeIt(add)##实例赋值
    print(a(4,5))##实例调用,这两句可以写为print(TimeIt(add)(4,5))

    通过以上两例,可以感觉到,类十分象一个装饰器,其实类也可以作为装饰器。

    import time,datetime
    from functools import update_wrapper
    class TimeIt:
        '''This is wtapper function'''
        def __init__(self,fn):
            self.fn = fn
            update_wrapper(self,fn)
    
        def __call__(self, *args, **kwargs):
            start = datetime.datetime.now()
            ret = self.fn(*args, *kwargs)
            print((datetime.datetime.now() - start).total_seconds())
            return ret
    
    @TimeIt
    def add (x,y):##创建实例对象时,调用__init__方法,完成后add指向类的对象,类对象的fn保存函数信息
        '''This is add function'''
        time.sleep(1)
        return x+y
    
    print(add(3,2))##调用函数时,调用__call__方法,装饰的方法在call方法里
  • 相关阅读:
    查看SQL语句执行时间、IO开销
    创建性能监视器(logman)
    IIS连接数
    SQL Server重建索引计划
    删除不存在的网卡
    授予普通域用户远程桌面连接DC/客户端权限
    AD新建用户、组、OU
    常用短语
    Android之APP模块编译
    Web&网络协议
  • 原文地址:https://www.cnblogs.com/rprp789/p/9545459.html
Copyright © 2020-2023  润新知