装饰器(decorator)
装饰器(decorator): 装饰器本质上使用的机制是闭包,只是语法上更方便,是python提供的语法糖。
回顾闭包
让我们先回顾一下闭包的简单情况:
import time
def decorator(fun):
print('装饰器开始执行')
def wrapper():
start_time = time.time()
fun()
end_time = time.time()
print('函数运行时长', end_time - start_time)
print('装饰器执行结束')
return wrapper
def foo():
print('foo 函数开始执行')
time.sleep(2)
print('foo say: hello world')
print('foo 函数执行结束')
new_fun = decorator(foo) # 这一句在执行完时就已经输出了两个print
new_fun() # 执行封装的闭包
输出结果:
装饰器开始执行
装饰器执行结束
foo 函数开始执行
foo say: hello world
foo 函数执行结束
函数运行时长 2.0009841918945312
简单装饰器
换成装饰器的写法:
def decorator(fun):
print('装饰器开始执行')
def wrapper():
start_time = time.time()
fun()
end_time = time.time()
print('函数运行时长', end_time - start_time)
print('装饰器执行结束')
return wrapper
@decorator
def foo():
print('foo 函数开始执行')
time.sleep(2)
print('foo say: hello world')
print('foo 函数执行结束')
# 注意,在没执行到 foo()这行代码之前,就已经打印了两个print,证明 @decorator 装饰符其实内部已经做了一个 foo = decorator(foo) 的操作。
foo()
输出结果:
装饰器开始执行
装饰器执行结束
foo 函数开始执行
foo say: hello world
foo 函数执行结束
函数运行时长 2.001372814178467
通用装饰器
通用装饰器,即无论被修饰的函数有多少个参数,都能将它正确封装并返回一个有相同参数的新函数。
import time
def decorator(fun):
print('装饰器开始执行')
def wrapper(*args, **kwargs):
start_time = time.time()
fun(*args, **kwargs)
end_time = time.time()
print('函数运行时长', end_time - start_time)
print('装饰器执行结束')
return wrapper
@decorator
def foo(secs):
print('foo 函数开始执行')
time.sleep(secs)
print('foo say: hello world')
print('foo 函数执行结束')
foo(3)
输出结果:
装饰器开始执行
装饰器执行结束
foo 函数开始执行
foo say: hello world
foo 函数执行结束
函数运行时长 3.001002311706543
多个装饰器叠加装饰
当有多个装饰器同时对一个函数装饰时,距离函数近的装饰器先被执行,直到所以装饰器完成装饰。
def double(f):
def wrapper(*args, **kwargs):
ret = f(*args, **kwargs) * 2
return ret
return wrapper
def local_square(f):
def wrapper(*args, **kwargs):
ret = f(*args, **kwargs) * f(*args, **kwargs)
return ret
return wrapper
@double
def foo(x):
return x
@local_square
def bar(x):
return x
@local_square
@double
def test(x):
return x
print(foo(3))
print(bar(4))
print(test(3)) # 叠加装饰效果。 先double,后local_square,结果是36。如果反过来,结果就应该是18了。
输出结果:
6
16
36
带参数的装饰器
装饰函数默认就需要有一个参数来指向被装饰函数的引用。这时如果想再加一个参数,以对装饰效果做灵活的控制该如何实现呢。
假设我们有一个简单的函数,输入一个值就返回一个值,然后我们希望有一个装饰函数,对这原函数返回的值再乘以一个倍数,并且这个倍数是一个可以传递的参数。
先用闭包的方式实现
def multiple(f, multi):
def wrapper(*args, **kwargs):
return f(*args, **kwargs) * multi
return wrapper
def foo(x):
return x
new_foo = multiple(foo, 5)
print(new_foo(2))
输出结果:
10
再用带参数的装饰器来实现
def outter(multi):
def inner(f):
def wrapper(*args, **kwargs):
return f(*args, **kwargs) * multi
return wrapper
return inner
@outter(5)
def foo(x):
return x
print(foo(2))
输出结果:
10
可以看出,带参数的装饰器,相当于先执行了外层函数并传递那个参数:outter(5),其返回的就是一个普通的装饰器(即只有一个形参用来接收被装饰的函数引用)。然后再用这个普通的装饰器对函数进行装饰。
类装饰器
把类当做装饰器,相当于类名(函数引用),生成了类实例后再执行 实例名(函数引用)
class Decorator:
def __init__(self, f):
def wrapper(*args, **kwargs):
return f(*args, **kwargs) * 5
self._f = wrapper
def __call__(self, *args, **kwargs):
return self._f(*args, **kwargs)
def bar(x):
return x
dec = Decorator(bar) # 类名加括号的调用,相当于实例化一个对象
print(dec(2)) # 实例加括号的调用,相当于调用call方法
@Decorator
def foo(x):
return x
print(foo(2))
输出结果:
10
10
带参数的类装饰器
class Decorator:
def __init__(self, multi):
self._multi = multi
def __call__(self, f):
def wrapper(*args, **kwargs):
return f(*args, **kwargs) * self._multi
return wrapper
def bar(x):
return x
obj_dec = Decorator(5) # 类名加括号的调用,相当于实例化一个对象
new_bar = obj_dec(bar) # 对象加括号的调用,相当于调用call方法,这里call方法返回一个装饰器函数
print(new_bar(2))
@Decorator(5)
def foo(x):
return x
print(foo(2))
输出结果:
10
10