装饰器是可调用的对象,其参数为一个函数(被装饰的函数),装饰器可能会处理被装饰的函数,然后把它返回,或将其替换成另一个函数或者对象,示例如下:
@decorate
def target():
print("run target")
target()
def target():
print("run target")
target = decorate(target)
target()
上述两种写法得到的结果一样,最终执行的并不一定是target函数,而是decorate(target)返回的函数
比如我们把decorate定义为如下函数
def decorate(fun):
print("run in decorate")
def wrapper():
print('run in wrapper')
fun()
print('finish run fun')
return wrapper
@decorate
def target():
print("run target")
print('start run target')
target()
可以看到执行结果如下:
run in decorate
start run target
run in wrapper
run target
finish run fun
从执行结果中可以看出结果问题
target = decorate(target)
target()
实际执行的是wrapper()
,即decorate返回的函数从上述例子已经看出,装饰器在被装饰的函数定义的时候立即执行,一般是在模块导入的时候(即Python加载模块的时候)
可能会有人问被装饰的函数需要传递参数怎么处理,可以在定义wrapper函数的时候传入参数,示例如下:
def decorate(fun):
def wrapper(name):
fun(name)
return wrapper
@decorate
def target(name):
print("run target, name = %s" % name)
print('start run target')
target('test_name')
[输出结果]
start run target
run target, name = test_name
如果需要传多个参数,甚至参数个数不确定呢?可以使用*args、**kwargs,跟函数使用基本一致
def decorate(fun):
def wrapper(**kargs):
fun(**kargs)
return wrapper
@decorate
def target(name = '', age = 1, num = 1):
print("name = %s" % name)
print("age = %d" % age)
print("num = %d" % num)
print('start run target')
target(name = 'test_name', age = 20, num = 10)
[输出结果]
start run target
name = test_name
age = 20
num = 10
def decorate(fun):
def wrapper(*args, **kargs):
fun(*args, **kargs)
return wrapper
@decorate
def target(name, age = 1, num = 1):
print("name = %s" % name)
print("age = %d" % age)
print("num = %d" % num)
print('start run target')
target('test_name', age = 20, num = 10)
[输出结果]
start run target
name = test_name
age = 20
num = 10
对于装饰器也需要参数,示例如下:
def using_level(level):
print('level = %s' % level)
def decorate(fun):
print('run in decorate')
def wrapper(*args, **kargs):
fun(*args, **kargs)
return wrapper
return decorate
@using_level("level2")
def target(name, age = 1, num = 1):
print("name = %s" % name)
print("age = %d" % age)
print("num = %d" % num)
print('start run target')
target('test_name', age = 20, num = 10)
[输出结果]
level = level2
run in decorate
start run target
name = test_name
age = 20
num = 10
从输出结果可以看出,在被装饰函数定义的时候,等同于调用
target = using_level("level2")(target)
,装饰器会自动发现传入的函数,并将被装饰函数传入到wrapper
装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def bar():
print ('bar')
bar()
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、name、参数列表,先看例子:
# 装饰器
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ # 输出 'with_logging'
print func.__doc__ # 输出 None
return func(*args, **kwargs)
return with_logging
# 函数
@logged
def f(x):
"""does some math"""
return x + x * x
logged(f)
不难发现,函数 f 被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器里面的 func 函数中,这使得装饰器里面的 func 函数也有和原函数 foo 一样的元信息了。
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ # 输出 'f'
print func.__doc__ # 输出 'does some math'
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
@a
@b
@c
def f ():
pass
它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于
f = a(b(c(f)))
【参考】https://foofish.net/python-decorator.html