装饰器本质以及装饰器产生的问题
装饰器介绍
装饰器的本质:一个闭包函数
装饰器的功能:在不修改原函数及其调用方式的情况下对原函数功能进行扩展
一个简单的装饰器实例
def time(func):#定义一个装饰器,接收一个函数作为参数 def inner(*args,**kwargs):#在装饰器内定义一个内部函数 """执行函数之前要做的""" re = func(*args,**kwargs)#被装饰的函数并且要执行,并接受结果 """执行函数之后要做的""" return re#返回原函数的运行结果 return inner#返回内部函数名 def func(): pass func = time(func) # 此时func ==inner
python为我们提供了简单写法
def time(func): def inner(*args,**kwargs): """执行函数之前要做的""" re = func(*args,**kwargs) """执行函数之后要做的""" return re#返回原函数的运行结果 return inner @time def func(): pass # @time就等于func = time(func),下面要紧接函数 #当再次调用函数func是实际调用的是inner #接收到的参数会被inner(*args,**kwargs)接受,并存储到元祖或字典中 #而args或者kwargs在传递到原函数时又会被分解 #从而实现了不改变函数调用方式,添加了新功能
当我们明白装饰器的本质之后,就可以去搞搞更多的装饰器,比如带参数的装饰器,以及用类写一个装饰器
带参数的函数装饰器:@xxx()就相当于xxx()的返回结果还可以接受一个函数被调用,然后再返回一个函数
def deco(arg): def _deco(func): def __deco(): print("before %s called [%s]." % (func.__name__, arg)) func() print(" after %s called [%s]." % (func.__name__, arg)) return __deco return _deco @deco("mymodule") def myfunc(): print(" myfunc() called.")
用类实现一个装饰器:func = Clsaa(func),func()就是执行了Class中的__call__
class ttl_func: def __init__(self,ttl_property): self.ttl_property=ttl_property def __call__(self, *args, **kwargs): # 函数执行前做的 ret = self.ttl_property( *args, **kwargs) # 函数执行后做的 return ret @ttl_property def cc(): print(time.time())
装饰器产生的问题
def outer(func): def inner(*args,**kwargs): """我是装饰器里的函数""" func(*args,**kwargs) return inner @outer def function(): """我是被装饰的函数""" print("哈哈哈") print(function.__name__) # 函数名 print(function.__doc__) # 函数注释 # 打印了装饰其中的内容,function其实就是outer(function) 就是inner函数 # inner # 我是装饰器里的函数 # 修复方法 from functools import wraps def outer(func): @wraps(func) def inner(*args,**kwargs): """我是装饰器里的函数""" func(*args,**kwargs) return inner @outer def function(): """我是被装饰的函数""" print("哈哈哈") print(function.__name__) # 函数名 print(function.__doc__) # 函数注释 #function #我是被装饰的函数
来了解一下wraps是如何做到的,首先我们要介绍一下partial和update_wrapper函数
functools.partial函数
该函数接收一个函数func和一些位置参数和关键字参数,内部会将这些参数绑定给func并返回一个新的函数,如果一个关键字参数被重复传入,后面的值会覆盖前面的值
from functools import partial def add(x, y): return x+y # 关键字传入 add2 = partial(add, y=2) add3 = partial(add2, y=3) q = add2(3) # 这里将会输出5 p = add3(3) # 这里将会输出6 # 位置传入 add4 = partial(add, 2) add5 = partial(add4, 3) b = add2(3) # 这里将会输出5 d = add3() # 这里将会输出5
这个函数是使用C而不是Python实现的,但是官方文档中给出了Python实现的代码,如下所示,大家可以进行参考
def partial(func, *args, **keywords): # 设置的参数 def newfunc(*fargs, **fkeywords): # 调用的参数传入 newkeywords = keywords.copy() newkeywords.update(fkeywords) # 关键字参数的更新 return func(*args, *fargs, **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunc
functools.update_wrapper
源码
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__') WRAPPER_UPDATES = ('__dict__',) def update_wrapper(wrapper, wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES): for attr in assigned: try: value = getattr(wrapped, attr) # 从被修饰的函数中获得指定属性 except AttributeError: pass else: setattr(wrapper, attr, value) # 设置给修饰他的函数 for attr in updated: getattr(wrapper, attr).update(getattr(wrapped, attr, {})) # 更新修饰字典的键值 wrapper.__wrapped__ = wrapped return wrapper #返回修饰函数
使用update_wrapper来修复一个修饰器函数
from functools import update_wrapper def wrapper(f): def wrapper_function(*args, **kwargs): """这个是修饰函数""" return f(*args, **kwargs) update_wrapper(wrapper_function, f) # << 此处修复 return wrapper_function @wrapper def wrapped(): """这个是被修饰的函数""" print('wrapped') print(wrapped.__doc__) # 输出`这个是被修饰的函数` print(wrapped.__name__) # 输出`wrapped`
functools.wraps
源码:
def wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES): return partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated) @wraps def func(): pass # func = wraps(func) = partial(update_wrapper, wrapped=func,assigned=assigned, updated=updated) = 一个绑定好属性的函数