python总结(一):AOP与装饰器

如果有AOP的编程经验,理解Python的装饰器就是分分钟的事。既然是装饰器,那么对被装饰的对象来说,一定是功能得到了增强,按方法能增强的地方进行划分,又可以分为以下四类:
1. 方法调用前;
2. 方法调用后;
3. 方法调用前后(环绕);
4. 方法调用异常;

我们以一个简单的加法运算来进行说明,按方法的增强点依次进行功能增强,首先看加法的代码,非常简单:

def add (a, b):
    return a + b

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

在这里,最难的就是怎么获取方法的参数,不要期待有反射的方式,更不要企图通过方法的属性可以获取到参数(这句话我说错了,还真有这样的方法),一定要通过变长参数的方式(元组与字典)才能获取到所有的实参

现在继续执行添加注解与调用方式,就会出现你想要的结果,如下:

@before
def add (a, b):
    return a + b

# 自动会抛出错误
print add(-1, 2)

2. 方法调用后与装饰器参数

这一般用于对结果进行处理,如类型转换,将对象转换为JSON字符串,或者结果判断等,如下:

#  携带参数的写法
def after (num):
    #   注意嵌套层次
    def afterProxy(func):
        # 修改返回结果
        def addMore (*args):
            #  对结果进行包装
            result = func(* args) + num
            return result
        return addMore
    return afterProxy

请记住上面的调用层次,一定是先执行参数的方法,返回没有参数的装饰函数,最后返回封装后的结果函数,等同于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)

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

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)

经过异常装饰器处理后,你会返现,现在3除以0也不报错了,而是返回0,所以这适合统一的异常处理。

5. 装饰器的顺序

经过上面的例子,我们明白,装饰器就是一个个的函数,而它们声明的顺序也就是函数调用的顺序,所以如果它们对函数的返回结果与参数非常敏感,那么一定要仔细斟酌它们的顺序,以上面的例子为例,如果将装饰器的顺序改为如下,则会出现参数合规错误:

@after(5)
@before
@round
def add (a, b):
    return a + b

为什么呢?因为执行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. 结合AOP的切入点理解装饰器会更容易。

你可能感兴趣的:(python实战技巧)