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方法里