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

来源:互联网 发布:雅思8.5 知乎 编辑:程序博客网 时间:2024/06/05 22:35

如果有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

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

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

@beforedef 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)@beforedef add (a, b):    return a + b#  返回结果为8print 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@rounddef add (a, b):    return a + b

为什么呢?因为执行round函数后,返回结果与参数都为None,所以执行after函数必然出错。

6. 能保留原始信息的@wraps

在很多权威的书籍里,都推荐大家使用@wraps注解,这样能保留方法的原始信息,例如“name”,“doc”等,甚至还能wrapped属性对包装的函数进行还原(据我测试多次,无法实现,版本2.7.14),写法如下:

#   引入wrapsfrom functools import wrapsdef 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@beforedef add (a, b):    return a + b# 现在可以获取函数的元数据信息print add.__name__print add.__doc__

结论

  1. 装饰器就是函数;
  2. 在携带参数时,请记得再务必包装一层;
  3. 装饰器注重调用的顺序,所以在装饰器的设计中,最好有详细的设计规范。
  4. 结合AOP的切入点理解装饰器会更容易。
原创粉丝点击