我没有学过太多语言,所以python的装饰器是我见过的最灵活的。—— 松言松语
装饰器
装饰器的前提
顾名思义,装饰类就是用一个装饰函数a来修饰另外一个函数b,使得函数b的功能得到增强。
在java(编译型语言)中,增强一个函数可以通过AOP实现,有两种方式:
1. 编译期,是通过修改字节码实现,如AspectJ;
2. 运行期,是通过生成一个代理对象实现的,如Spring AOP;
python是通过闭包来做的。
闭包:
一个函数(就叫它外函数吧)里面定义了一个函数(称之为内函数),内函数中用使用到了外函数中的变量,并且,外函数直接返回了这个内函数的引用
简单来说,就是 函数套函数,函数返回函数
,举个例子
def f1(x, y):
result = x + y
def f2():
print(result)
return result
return f2
y = f1(1, 2)
y()
# output :
可以看到f1中“套了”一个f2,f2用到了f1中的变量,f1返回了f2。这就是闭包。理解这个,下面再来看一下python的装饰器。
装饰器函数
from functools import wraps
def decorator(func):
"""定义一个装饰器函数"""
@wraps(func)
def decoration_function(*args, **kwargs):
print("start...")
func(*args, **kwargs)
print("end...")
return decoration_function;
@decorator
def test():
"""被装饰的方法"""
print("running")
test()
一共有三个函数,一个是被装饰的函数(test),外函数(decorator)、内函数(decoration_function);
内函数用
@wraps(被修饰函数)
来修饰。其中,func就是被修饰的函数的变量表示;被修饰的函数用
@外函数
修饰如,@decorator;被装饰的函数(test)作为外函数(decorator)的一个参数(func),传给内函数(decoration_function)并在内函数中执行;
外函数(decorator)返回内函数(decoration_function)的引用;
总结一下步骤,就是3-2法则:
3函数。函数套函数,外函数返回内函数;
2注解。
@wraps(被修饰函数)
、@外函数;
需要提醒一下的是,
@decorator
是一个语法糖,只是为了简化操作,从而看起来更像一个装饰器@decorator def test(): """被装饰的方法""" print("running") test()
等价于
def test(): """被装饰的方法""" print("running") y = decorator(test) y()
实际上,就是把被装饰函数test作为参数传到装饰器里面去。
@wraps
的作用,是为了维持被装饰函数(test)执行的时候,使其函数性质保持不变。正因为
@decorator
是语法糖,所以执行的时候可能存在一些问题。def decorator(func): """定义一个装饰器函数""" def decoration_function(*args, **kwargs): print("start...") func(*args, **kwargs) print("end...") return decoration_function @decorator def test(): """被装饰的方法""" print("running") print(test.__name__) # output : decoration_function
去掉
@wraps(func)
执行,你会发现 被装饰函数 test 其实就是闭包里返回的内函数decoration_function嘛(其实这也反向证明了@decorator
是语法糖),但我们期望的输出应该是test,这不科学!test的名字、属性等都变了(虽然执行结果一样),这还能叫装饰器吗?叫做代理可能会更合理一些。
所以python中提供了
@wraps(被修饰函数)
,是为了声明这是一个修饰器的内函数,并复制了被修饰函数原有的名字、属性等。
装饰器类
装饰器还可以以class的形式构建。
如下所示
from functools import wraps
import time
class TimeWrapper: # 计算一个方法的执行时间
def __init__(self, name):
self.name = name
def __call__(self, func):
"""
提供__call__的原因,是把TimeWrapper变成一个可直接执行的类;
调用类的时候,会触发__call__中的方法,参数也是通过__call__传进来;
"""
@wraps(func)
def wrapped_function(*args, **kwargs):
print('start....')
start_time = time.perf_counter()
result = func(*args, **kwargs)
print('end....')
print(f'运行时间:{time.perf_counter() - start_time:8f}s')
return result
return wrapped_function
# 通过注解的形式来调用装饰器类TimeWrapper,注意,此时,默认调用的是类中的__call__
@TimeWrapper('looping')
def loop(n):
for i in range(1, n):
print(i)
loop(4)
要点是:
- 要在类中定义一个
__call__
函数,以确保类是可执行的,调用的时候,自动执行__call__
函数; __call__
作为外函数存在;- 使用修饰器的时候,用
@类名
;