zoukankan      html  css  js  c++  java
  • 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方法里
  • 相关阅读:
    我的知识库(4) java获取页面编码(Z)
    知识库(3)JAVA 正则表达式 (超详细)
    The Struts dispatcher cannot be found. This is usually caused by using Struts tags without the associated filter. Struts
    某人总结的《英语听力的技巧 》,挺搞的
    我的知识库(5)java单例模式详解
    构建可扩展程序
    SerialPort (RS232 Serial COM Port) in C# .NET
    Python学习笔记——String、Sequences
    UI题目我的答案
    jQuery学习系列学会操纵Form表单元素(1)
  • 原文地址:https://www.cnblogs.com/rprp789/p/9545459.html
Copyright © 2011-2022 走看看