谁能够做为装饰器(能够将谁编写成装饰器):html
__call__
的可调用类装饰器能够去装饰谁(谁能够被装饰):python
假如你已经定义了一个函数funcA(),在准备定义函数funcB()的时候,若是写成下面的格式:app
@funcA def funcB():...
表示用函数funcA()装饰函数funcB()。固然,也能够认为是funcA包装函数funcB。它等价于:函数
def funcB():... funcB = funcA(funcB)
也就是说,将函数funcB做为函数funcA的参数,funcA会从新返回另外一个可调用的对象(好比函数)并赋值给funcB。编码
因此,funcA要想做为函数装饰器,须要接收函数做为参数,而且返回另外一个可调用对象(如函数)。例如:code
def funcA(F): ... ... return Callable
注意,函数装饰器返回的可调用对象并不必定是原始的函数F,能够是任意其它可调用对象,好比另外一个函数。但最终,这个返回的可调用对象都会被赋值给被装饰的函数变量(上例中的funcB)。htm
函数能够同时被多个装饰器装饰,后面的装饰器之前面的装饰器处理结果为基础进行处理:对象
@decorator1 @decorator2 def func():... # 等价于 func = decorator1(decorator2(func))
当调用被装饰后的funcB时,将自动将funcB进行装饰,并调用装饰后的对象。因此,下面是等价的调用方式:blog
funcB() # 调用装饰后的funcB funcA(funcB)()
了解完函数装饰器的表现后,大概也能猜到了,装饰器函数能够用来扩展、加强另一个函数。实际上,内置函数中staticmethod()、classmethod()和property()都是装饰器函数,能够用来装饰其它函数,在后面会学到它们的用法。字符串
例如,函数f()返回一些字符串,如今要将它的返回结果转换为大写字母。能够定义一个函数装饰器来加强函数f()。
def toupper(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result.upper() return wrapper @toupper def f(x: str): # 等价于f = toupper(f) return x res = f("abcd") print(res)
上面toupper()装饰f()后,调用f("abcd")
的时候,等价于执行toupper(f)("abcd")
,参数"abcd"传递给装饰器中的wrapper()中的*args
,在wrapper中又执行了f("abcd")
,使得本来属于f()的整个过程都完整了,最后返回result.upper()
,这部分是对函数f()的扩展部分。
注意,上面的封装函数wrapper()中使用了*args **kwargs
,是为了确保任意参数的函数都能正确执行下去。
再好比要计算一个函数autodown()的执行时长,能够额外定义一个函数装饰器timecount()。
import time # 函数装饰器 def timecount(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result return wrapper # 装饰函数 @timecount def autodown(n: int): while n > 0: n -= 1 # 调用被装饰的函数 autodown(100000) autodown(1000000) autodown(10000000)
执行结果:
autodown 0.004986763000488281 autodown 0.05684685707092285 autodown 0.5336081981658936
上面wrapper()中的return是多余的,是由于这里装饰的autodown()函数自身没有返回值。但却不该该省略这个return,由于timecount()能够去装饰其它可能有返回值的函数。
前面的装饰器代码逻辑上没有什么问题,可是却存在隐藏的问题:函数的元数据信息丢了。好比doc、注解等。
好比下面的代码:
@timecount def autodown(n: int): ''' some docs ''' while n > 0: n -= 1 print(autodown.__name__) print(autodown.__doc__) print(autodown.__annotations__)
执行结果为:
wrapper None {}
因此,必需要将被装饰函数的元数据保留下来。可使用functools模块中的wraps()装饰一下装饰器中的wrapper()函数。以下:
import time from functools import wraps def timecount(func): @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result return wrapper
如今,再去查看autodown函数的元数据信息,将会获得被保留下来的内容:
autodown some doc {'n': <class 'int'>}
因此,wraps()的简单用法是:向wraps()中传递的func参数,那么func的元数据就会被保留下来。
上面@wraps(func)
装饰wrapper的过程等价于:
def wrapper(*args, **kwargs):... wrapper = wraps(func)(wrapper)
请注意这一点,由于在将类做为装饰器的时候,常常会在__init__(self, func)
里这样使用:
class cls: def __init__(self, func): wraps(func)(self) ... def __call__(self, *args, **kwargs): ...
函数被装饰后,如何再去访问未被装饰状态下的这个函数?@wraps还有一个重要的特性,能够经过被装饰对象的__wrapped__
属性来直接访问被装饰对象。例如:
autodown.__wrapped__(1000000) new_autodown = autodown.__wrapped__ new_autodown(1000000)
上面的调用不会去调用装饰后的函数,因此不会输出执行时长。
注意,若是函数被多个装饰器装饰,那么经过__wrapped__
,将只会解除第一个装饰过程。例如:
@decorator1 @decorator2 @decorator3 def f():...
当访问f.__wrapped__()
的时候,只有decorator1被解除,剩余的全部装饰器仍然有效。注意,python 3.3以前是略过全部装饰器。
下面是一个多装饰的示例:
from functools import wraps def decorator1(func): @wraps(func) def wrapper(*args, **kwargs): print("in decorator1") return func(*args, **kwargs) return wrapper def decorator2(func): @wraps(func) def wrapper(*args, **kwargs): print("in decorator2") return func(*args, **kwargs) return wrapper def decorator3(func): @wraps(func) def wrapper(*args, **kwargs): print("in decorator3") return func(*args, **kwargs) return wrapper @decorator1 @decorator2 @decorator3 def addNum(x, y): return x+y
返回结果:
in decorator1 in decorator2 in decorator3 5 in decorator2 in decorator3 5
若是不使用functools的@wraps的__wrapped__
,想要手动去引用原始函数,须要作的工做可能会很是多。因此,若有须要,直接使用__wrapped__
去调用未被装饰的函数比较好。
另外,并非全部装饰器中都使用了@wraps
。
函数装饰器也是能够带上参数的。
@decorator(x,y,z) def func():...
它等价于:
func = decorator(x,y,z)(func)
它并非"天生"就这样等价的,而是根据编码规范编写装饰器的时候,一般会这样。其实带参数的函数装饰器写起来有点绕:先定义一个带有参数的外层函数,它是外在的函数装饰器,这个函数内包含了真正的装饰器函数,而这个内部的函数装饰器的内部又包含了被装饰的函数封装。也就是函数嵌套了一次又一次。
因此,结构大概是这样的:
def out_decorator(some_args): ...SOME CODE... def real_decorator(func): ...SOME CODE... def wrapper(*args, **kwargs): ...SOME CODE WITH func... return wrapper return real_decorator # 等价于func = out_decorator(some_args)(func) @out_decorator(some_args) def func():...
下面是一个简单的例子:
from functools import wraps def out_decorator(x, y, z): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(x) print(y) print(z) return func(*args, **kwargs) return wrapper return decorator @out_decorator("xx", "yy", "zz") def addNum(x, y): return x+y print(addNum(2, 3))
根据前面介绍的两种状况,装饰器能够带参数、不带参数,因此有两种装饰的方式,要么是下面的(1),要么是下面的(2)。
@decorator # (1) @decorator(x,y,z) # (2)
因此,根据不一样的装饰方式,须要编写是否带参数的不一样装饰器。
可是如今想要编写一个将上面两种参数方式统一块儿来的装饰器。
可能第一想法是让装饰器参数默认化:
def out_decorator(arg1=X, arg2=Y...): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): ... return wrapper return decorator
如今能够用下面两种方式来装饰:
@out_decorator() @out_decorator(arg1,arg2)
虽然上面两种装饰方式会正确进行,但这并不是合理作法,由于下面这种最通用的装饰方式会错误:
@out_decorator
为了解决这个问题,回顾下前面装饰器是如何等价的:
# 等价于 func = decorator(func) @decorator def func():... # 等价于 func = out_decorator(x, y, z)(func) @out_decorator(x, y, z) def func():...
上面第二种方式中,out_decorator(x,y,z)才是真正返回的内部装饰器。因此,能够修改下装饰器的编写方式,将func也做为out_decorator()的其中一个参数:
from functools import wraps,partial def decorator(func=None, arg1=X, arg2=Y): # 若是func为None,说明触发的带参装饰器 # 直接返回partial()封装后的装饰器函数 if func is None: decorator_new = partial(decorator, arg1=arg1, arg2=arg2) return decorator_new #return partial(decorator, arg1=arg1, arg2=arg2) # 下面是装饰器的完整装饰内容 @wraps(func) def wrapper(*args, **kwargs): ... return wrapper
上面使用了functools模块中的partial()函数,它能够返回一个新的将某些参数"冻结"后的函数,使得新的函数无需指定这些已被"冻结"的参数,从而减小参数的数量。若是不知道这个函数,参考partial()用法说明。
如今,能够统一下面3种装饰方式:
@decorator() @decorator(arg1=x,arg2=y) @decorator
前两种装饰方式,等价的调用方式是decorator()(func)
和decorator(arg1=x,arg2=y)(func)
,它们的func都为None,因此都会经过partial()返回一般的装饰方式@decorator
所等价的形式。
须要注意的是,由于上面的参数结构中包含了func=None
做为第一个参数,因此带参数装饰时,必须使用keyword格式来传递参数,不能使用位置参数。
下面是一个简单的示例:
from functools import wraps, partial def decorator(func=None, x=1, y=2, z=3): if func is None: return partial(decorator, x=x, y=y, z=z) @wraps(func) def wrapper(*args, **kwargs): print("x: ", x) print("y: ", y) print("z: ", z) return func(*args, **kwargs) return wrapper
下面3种装饰方式均可以:
@decorator def addNum(a, b): return a + b print(addNum(2, 3)) print("=" * 40) @decorator() def addNum(a, b): return a + b print(addNum(2, 3)) print("=" * 40) # 必须使用关键字参数进行装饰 @decorator(x="xx", y="yy", z="zz") def addNum(a, b): return a + b print(addNum(2, 3))
返回结果:
x: 1 y: 2 z: 3 5 ==================== x: 1 y: 2 z: 3 5 ==================== x: xx y: yy z: zz 5