您现在的位置是:网站首页> 编程资料编程资料
Python 面向切面编程 AOP 及装饰器_python_
2023-05-26
367人已围观
简介 Python 面向切面编程 AOP 及装饰器_python_
什么是 AOP
AOP,就是面向切面编程,简单的说,就是动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。这样我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。这种思想,可以使原有代码逻辑更清晰,对原有代码毫无入侵性,常用于像权限管理,日志记录,事物管理等等。而 Python 中的装饰器就是很著名的设计,常用于有切面需求的场景。类如,Django 中就大量使用装饰器去完成一下切面需求,如权限控制,内容过滤,请求管理等等。
装饰器
Python 装饰器(fuctional decorators)就是用于拓展原来函数功能的一种函数,目的是在不改变原函数名或类名的情况下,给函数增加新的功能。
下面就跟我一起详细的了解下装饰器是如何工作的。首先,要明确一个概念:Python 中万物皆对象,函数也是是对象!所以,一个函数作为对象,可以在另一个函数中定义。
看下面示例:
def a(): def b(): print("I'm b") b() c = b return c d = a() d() b() c()输出结果为:
I'm b
I'm b
抛出 NameError: name 'b' is not defined 错误
抛出 NameError: name 'c' is not defined 错误
从上可以看出,由于函数是对象,所以:
- 可以赋值给变量
- 可以在另一个函数中定义
然后,return 可以返回一个函数对象。这个函数对象是在另一个函数中定义的。由于作用域不同,所以只有 return 返回的函数可以调用,在函数 a 中定义和赋值的函数 b 和 c 在外部作用域是无法调用的。
这意味着一个功能可以 return 另一个功能。
除了可以作为对象返回外,函数对象还可以作为参数传递给另一个函数:
def a(): print("I'm a") def b(func): print("I'm b") func() b(a)输出结果:
I'm b
I'm a
OK,现在,基于函数的这些特性,我们就可以创建一个装饰器,用来在不改变原函数的情况下,实现功能。
函数装饰器
比如,我们要在函数执行前和执行后分别执行一些别的操作,那么根据上面函数可以作为参数传递,我们可以这样实现,看下面示例:
def a(): print("I'm a") def b(func): print('在函数执行前,做一些操作') func() print("在函数执行后,做一些操作") b(a)输出结果:
在函数执行前,做一些操作
I'm a
在函数执行后,做一些操作
但是这样的话,原函数就变成了另一个函数,每加一个功能,就要在外面包一层新的函数,这样原来调用的地方,就会需要全部修改,这明显不方便,就会想,有没有办法可以让函数的操作改变,但是名称不改变,还是作为原函数呢。
看下面示例:
def a(): print("I'm a") def c(func): def b(): print('在函数执行前,做一些操作') func() print("在函数执行后,做一些操作") return b a = c(a) a()输出结果:
在函数执行前,做一些操作
I'm a
在函数执行后,做一些操作
如上,我们可以将函数再包一层,将新的函数 b,作为对象返回。这样通过函数 c,将 a 改变并重新赋值给 a,这样就实现了改变函数 a,并同样使用函数 a 来调用。
但是这样写起来非常不方便,因为需要重新赋值,所以在 Python 中,可以通过 @ 来实现,将函数作为参数传递。
看下示例:
def c(func): def b(): print('在函数执行前,做一些操作') func() print("在函数执行后,做一些操作") return b @c def a(): print("I'm a") a()输出结果:
在函数执行前,做一些操作
I'm a
在函数执行后,做一些操作
如上,通过 @c,就实现了将函数 a 作为参数,传入 c,并将返回的函数重新作为函数 a。这 c 也就是一个简单的函数装饰器。
且如果函数是有返回值的,那么改变后的函数也需要有返回值,如下所以:
def c(func): def b(): print('在函数执行前,做一些操作') result = func() print("在函数执行后,做一些操作") return result return b @c def a(): print("函数执行中。。。") return "I'm a" print(a())输出结果:
在函数执行前,做一些操作
函数执行中。。。
在函数执行后,做一些操作
I'm a
如上所示:通过将返回值进行传递,就可以实现函数执行前后的操作。但是你会发现一个问题,就是为什么输出 I'm a 会在最后才打印出来?
因为 I'm a 是返回的结果,而实际上函数是 print("在函数执行后,做一些操作") 这一操作前运行的,只是先将返回的结果给到了 result,然后 result 传递出来,最后由最下方的 print(a()) 打印了出来。
那如何函数 a 带参数怎么办呢?很简单,函数 a 带参数,那么我们返回的函数也同样要带参数就好啦。
看下面示例:
def c(func): def b(name, age): print('在函数执行前,做一些操作') result = func(name, age) print("在函数执行后,做一些操作") return result return b @c def a(name, age): print("函数执行中。。。") return "我是 {}, 今年{}岁 ".format(name, age) print(a('Amos', 24))输出结果:
在函数执行前,做一些操作
函数执行中。。。
在函数执行后,做一些操作
我是 Amos, 今年24岁
但是又有问题了,我写一个装饰器 c,需要装饰多个不同的函数,这些函数的参数各不相同,那么怎么办呢?简单,用 *args 和 **kwargs 来表示所有参数即可。
如下示例:
def c(func): def b(*args, **kwargs): print('在函数执行前,做一些操作') result = func(*args, **kwargs) print("在函数执行后,做一些操作") return result return b @c def a(name, age): print('函数执行中。。。') return "我是 {}, 今年{}岁 ".format(name, age) @c def d(sex, height): print('函数执行中。。。') return '性别:{},身高:{}'.format(sex, height) print(a('Amos', 24)) print(d('男', 175)) 输出结果:
在函数执行前,做一些操作
函数执行中。。。
在函数执行后,做一些操作
我是 Amos, 今年24岁在函数执行前,做一些操作
函数执行中。。。
在函数执行后,做一些操作
性别:男,身高:175
如上就解决了参数的问题,哇,这么好用。那是不是这样就没有问题了?并不是!经过装饰器装饰后的函数,实际上已经变成了装饰器函数 c 中定义的函数 b,所以函数的元数据则全部改变了!
如下示例:
def c(func): def b(*args, **kwargs): print('在函数执行前,做一些操作') result = func(*args, **kwargs) print("在函数执行后,做一些操作") return result return b @c def a(name, age): print('函数执行中。。。') return "我是 {}, 今年{}岁 ".format(name, age) print(a.__name__)输出结果:
b
会发现函数实际上是函数 b 了,这就有问题了,那么该怎么解决呢,有人就会想到,可以在装饰器函数中先把原函数的元数据保存下来,在最后再讲 b 函数的元数据改为原函数的,再返回 b。这样的确是可以的!但我们不这样用,为什么?
因为 Python 早就想到这个问题啦,所以给我们提供了一个内置的方法,来自动实现原数据的保存和替换工作。哈哈,这样就不同我们自己动手啦!
看下面示例:
from functools import wraps def c(func): @wraps(func) def b(*args, **kwargs): print('在函数执行前,做一些操作') result = func(*args, **kwargs) print("在函数执行后,做一些操作") return result return b @c def a(name, age): print('函数执行中。。。') return "我是 {}, 今年{}岁 ".format(name, age) print(a.__name__)输出结果:
a
使用内置的 wraps 装饰器,将原函数作为装饰器参数,实现函数原数据的保留替换功能。
耶!装饰器还可以带参数啊,你看上面 wraps 装饰器就传入了参数。哈哈,是的,装饰器还可以带参数,那怎么实现呢?
看下面示例:
from functools import wraps def d(name): def c(func): @wraps(func) def b(*args, **kwargs): print('装饰器传入参数为:{}'.format(name)) print('在函数执行前,做一些操作') result = func(*args, **kwargs) print("在函数执行后,做一些操作") return result return b return c @d(name='我是装饰器参数') def a(name, age): print('函数执行中。。。') return "我是 {}, 今年{}岁 ".format(name, age) print(a('Amos', 24)) 输出结果:
装饰器传入参数为:我是装饰器参数
在函数执行前,做一些操作
函数执行中。。。
在函数执行后,做一些操作
我是 Amos, 今年24岁
如上所示,很简单,只需要在原本的装饰器之上,再包一层,相当于先接收装饰器参数,然后返回一个不带参数的装饰器,然后再将函数传入,最后返回变化后的函数。
这样就可以实现很多功能了,这样可以根据传给装饰器的参数不同,来分别实现不同的功能。
另外,可能会有人问, 可以在同一个函数上,使用多个装饰器吗?答案是:可以!
看下面示例:
from functools import wraps def d(name): def c(func): @wraps(func) def b(*args, **kwargs): print('装饰器传入参数为:{}'.format(name)) print('我是装饰器d: 在函数执行前,做一些操作') result = func(*args, **kwargs) print("我是装饰器d: 在函数执行后,做一些操作") return result return b return c def e(name): def c(func): @wraps(func) def b(*args, **kwargs): print('装饰器传入参数为:{}'.format(name)) print('我是装饰器e: 在函数执行前,做一些操作') result = func(*args, **kwargs) print("我是装饰器e: 在函数执行后,做一些操作") return result return b return c @e(name='我是装饰器e') @d(name='我是装饰器d') def func_a(name, age): print('函数执行中。。。') return "我是 {}, 今年{}岁 ".format(name, age) print(func_a('Amos', 24)) 行后,做一些操作 我是 Amos, 今年24岁 输出结果:
装饰器传入参数为:我是装饰器e
我是装饰器e: 在函数执行前,做一些操作装饰器传入参数为:我是装饰器d
我是装饰器d: 在函数执行前,做一些操作
函数执行中。。。
我是装饰器d: 在函数执行后,做一些操作我是装饰器e: 在函数执
如上所示,当两个装饰器同时使用时,可以想象成洋葱,最下层的装饰器先包装一层,然后一直到最上层装饰器,完成多层的包装。然后执行时,就像切洋葱,从最外层开始,只执行到被装饰函数运行时,就到了下一层,下一层又执行到函数运行时到下一层,一直到执行了被装饰函数后,就像切到了洋葱的中间,然后再往下,依次从最内层开始,依次执行到最外层。
示例:
当一个函数 a 被 b,c,d 三个装饰器装饰时,执行顺序如下图所示,多个同理。
@d @c @b def a(): pass

类装饰器
在函数装饰器方面,很多人搞不清楚,是因为装饰器可以用函数实现(像上面),也可以用类实现。因为函数和类都是对象,同样可以作为被装饰的对象,所以根据被装饰的对象不同,一同有下面四种情况:
- 函数装饰函数
- 类装饰函数
- 函数装饰类
- 类装饰类
下面我们依次来说明一下这四种情况的使用。
1、函数装饰函数
from functools import wraps def d(name): def c(func): @wraps(func) def b(*args, **kwargs): print('装饰器传入参数为:{}'.format
相关内容
- Python的基本语法详解_python_
- Python包管理工具pip用法详解_python_
- Python通用验证码识别OCR库之ddddocr验证码识别_python_
- 使用python3.0 对接美团接口的实现示例_python_
- Python使用LRU缓存策略进行缓存的方法步骤_python_
- 详解Python常用标准库之时间模块time和datetime_python_
- 通过底层源码理解YOLOv5的Backbone_python_
- Python lambda函数使用方法深度总结_python_
- Python定时任务框架APScheduler安装使用详解_python_
- Python曲线平滑的实现示例_python_
点击排行
本栏推荐
