如果有AOP的编程经验,理解Python的装饰器就是分分钟的事。既然是装饰器,那么对被装饰的对象来说,一定是功能得到了增强,按方法能增强的地方进行划分,又可以分为以下四类:
1. 方法调用前;2. 方法调用后;
3. 方法调用前后(环绕);
4. 方法调用异常;
我们以一个简单的加法运算来进行说明,按方法的增强点依次进行功能增强,首先看加法的代码,非常简单:
def add (a, b):
return a + b
- 1
- 2
1. 方法调用前
在调用之前检查方法的参数是否合规,或者判断是否有相应的执行权限,这都是常用的手法,本例中检验第一个参数是否为正数,如果是,则执行加法,否则抛出错误,那我们可以定义如下的装饰器:
def before(func):
# 只能通过参数声明的方式获取参数
def check(a, *args):
# 如果小于0,抛出异常
if(a < 0):
raise Exception('a is less than zero!')
else:
return func(a, *args)
# 记住,返回的一定是函数
return check
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
在这里,最难的就是怎么获取方法的参数,不要期待有反射的方式,更不要企图通过方法的属性可以获取到参数(这句话我说错了,还真有这样的方法),一定要通过变长参数的方式(元组与字典)才能获取到所有的实参。
现在继续执行添加注解与调用方式,就会出现你想要的结果,如下:
@before
def add (a, b):
return a + b
# 自动会抛出错误
print add(-1, 2)
- 1
- 2
- 3
- 4
- 5
- 6
2. 方法调用后与装饰器参数
这一般用于对结果进行处理,如类型转换,将对象转换为JSON字符串,或者结果判断等,如下:
# 携带参数的写法
def after (num):
# 注意嵌套层次
def afterProxy(func):
# 修改返回结果
def addMore (*args):
# 对结果进行包装
result = func(* args) + num
return result
return addMore
return afterProxy
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
请记住上面的调用层次,一定是先执行参数的方法,返回没有参数的装饰函数,最后返回封装后的结果函数,等同于after(5)(before(add(a, b))),调用方式如下:
# 等同于after(5)(before(add(a, b)))
# 可以不同时使用,装饰之间没有依赖关系
@after(5)
@before
def add (a, b):
return a + b
# 返回结果为8
print add(1, 2)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
3. 方法调用前后(环绕)
这个方式应用场景最多,比如事务的管理、数据库连接的自动开始与关闭等,并且前两项装饰器能干的事情它都能干,所以也是最强大的装饰器。原理也很简单,就是在方法执行前做一些事情,在方法执行后做一些事情,如下:
# 定义环绕装饰
def round(func):
def roundProxy(*args):
print 'before execute'
print func(*args)
print 'end execute'
return roundProxy
# 执行调用
add(1, 2)
# 输出结果为
# before execute
# 8
# end execute
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
4. 方法调用异常
这种装饰器一般用于方法调用出现异常时,所以一定会携带装饰器参数,且参数为异常类型,然后处理逻辑会放在异常捕获语句中,示例如下:
# 定义异常装饰器
# ex为异常类型
def mathExcept(ex):
def exceptProxy(func):
def divFunc(*args):
try:
return func(*args)
# 捕获异常
except ex:
# 处理业务逻辑
return 0
return divFunc
return exceptProxy
# 添加异常装饰器
@mathExcept(ZeroDivisionError)
def div(a, b):
if(b == 0):
raise ZeroDivisionError('b is zero')
else:
return a / b
# 调用结果
print div(3, 0)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
经过异常装饰器处理后,你会返现,现在3除以0也不报错了,而是返回0,所以这适合统一的异常处理。
5. 装饰器的顺序
经过上面的例子,我们明白,装饰器就是一个个的函数,而它们声明的顺序也就是函数调用的顺序,所以如果它们对函数的返回结果与参数非常敏感,那么一定要仔细斟酌它们的顺序,以上面的例子为例,如果将装饰器的顺序改为如下,则会出现参数合规错误:
@after(5)
@before
@round
def add (a, b):
return a + b
- 1
- 2
- 3
- 4
- 5
为什么呢?因为执行round函数后,返回结果与参数都为None,所以执行after函数必然出错。
6. 能保留原始信息的@wraps
在很多权威的书籍里,都推荐大家使用@wraps注解,这样能保留方法的原始信息,例如“name”,“doc”等,甚至还能wrapped属性对包装的函数进行还原(据我测试多次,无法实现,版本2.7.14),写法如下:
# 引入wraps
from functools import wraps
def before(func):
@wraps(func)
def check(a, *args):
# 如果小于0,抛出异常
if(a < 0):
raise Exception('a is less than zero!')
else:
return func(a, *args)
# 记住,返回的一定是函数
return check
@before
def add (a, b):
return a + b
# 现在可以获取函数的元数据信息
print add.__name__
print add.__doc__
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
结论
- 装饰器就是函数;
- 在携带参数时,请记得再务必包装一层;
- 装饰器注重调用的顺序,所以在装饰器的设计中,最好有详细的设计规范。
- 结合AOP的切入点理解装饰器会更容易。