Python 面向切面编程 AOP 及装饰器
作者:??Python编程学习圈???? 发布时间:2021-05-07 14:16:36
什么是 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(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.__class__)
# 输出结果:
<class 'function'>
此为最常见的装饰器,用于装饰函数,返回的是一个函数。
2、类装饰函数
也就是通过类来实现装饰器的功能而已。通过类的 __call__
方法实现:
from functools import wraps
class D(object):
def __init__(self, name):
self._name = name
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('装饰器传入参数为:{}'.format(self._name))
print('在函数执行前,做一些操作')
result = func(*args, **kwargs)
print("在函数执行后,做一些操作")
return result
return wrapper
@D(name='我是装饰器参数')
def a(name, age):
print('函数执行中。。。')
return "我是 {}, 今年{}岁 ".format(name, age)
print(a.__class__)
# 输出结果:
<class 'function'>
以上所示,只是将用函数定义的装饰器改为使用类来实现而已。还是用于装饰函数,因为在类的 __call__
中,最后返回的还是一个函数。
此为带装饰器参数的装饰器实现方法,是通过 __call__
方法。
若装饰器不带参数,则可以将 __init__
方法去掉,但是在使用装饰器时,需要 @D()
这样使用,如下:
from functools import wraps
class D(object):
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('在函数执行前,做一些操作')
result = func(*args, **kwargs)
print("在函数执行后,做一些操作")
return result
return wrapper
@D()
def a(name, age):
print('函数执行中。。。')
return "我是 {}, 今年{}岁 ".format(name, age)
print(a.__class__)
# 输出结果:
<class 'function'>
如上是比较方便简答的,使用类定义函数装饰器,且返回对象为函数,元数据保留。
3、函数装饰类
下面重点来啦,我们常见的装饰器都是用于装饰函数的,返回的对象也是一个函数,而要装饰类,那么返回的对象就要是类,且类的元数据等也要保留。
不怕丢脸的说,目前我还不知道怎么实现完美的类装饰器,在装饰类的时候,一般有两种方法:
返回一个函数,实现类在创建实例的前后执行操作,并正常返回此类的实例。但是这样经过装饰器的类就属于函数了,其无法继承,但可以正常调用创建实例。
如下:
from functools import wraps
def d(name):
def c(cls):
@wraps(cls)
def b(*args, **kwargs):
print('装饰器传入参数为:{}'.format(name))
print('在类初始化前,做一些操作')
instance = cls(*args, **kwargs)
print("在类初始化后,做一些操作")
return instance
return b
return c
@d(name='我是装饰器参数')
class A(object):
def __init__(self, name, age):
self.name = name
self.age = age
print('类初始化实例,{} {}'.format(self.name, self.age))
a = A('Amos', 24)
print(a.__class__)
print(A.__class__)
# 输出结果:
装饰器传入参数为:我是装饰器参数
在类初始化前,做一些操作
类初始化实例,Amos 24
在类初始化后,做一些操作
<class '__main__.A'>
<class 'function'>
如上所示,就是第一种方法。
4、类装饰类
接上文,返回一个类,实现类在创建实例的前后执行操作,但类已经改变了,创建的实例也已经不是原本类的实例了。
看下面示例:
def desc(name):
def decorator(aClass):
class Wrapper(object):
def __init__(self, *args, **kwargs):
print('装饰器传入参数为:{}'.format(name))
print('在类初始化前,做一些操作')
self.wrapped = aClass(*args, **kwargs)
print("在类初始化后,做一些操作")
def __getattr__(self, name):
print('Getting the {} of {}'.format(name, self.wrapped))
return getattr(self.wrapped, name)
def __setattr__(self, key, value):
if key == 'wrapped': # 这里捕捉对wrapped的赋值
self.__dict__[key] = value
else:
setattr(self.wrapped, key, value)
return Wrapper
return decorator
@desc(name='我是装饰器参数')
class A(object):
def __init__(self, name, age):
self.name = name
self.age = age
print('类初始化实例,{} {}'.format(self.name, self.age))
a = A('Amos', 24)
print(a.__class__)
print(A.__class__)
print(A.__name__)
输出结果:
装饰器传入参数为:我是装饰器参数
在类初始化前,做一些操作
类初始化实例,Amos 24
在类初始化后,做一些操作
<class '__main__.desc.<locals>.decorator.<locals>.Wrapper'>
<class 'type'>
Wrapper
如上,看到了吗,通过在函数中新定义类,并返回类,这样函数还是类,但是经过装饰器后,类 A
已经变成了类 Wrapper
,且生成的实例 a
也是类 Wrapper
的实例,即使通过 __getattr__
和 __setattr__
两个方法,使得实例a的属性都是在由类 A
创建的实例 wrapped
的属性,但是类的元数据无法改变。很多内置的方法也就会有问题。我个人是不推荐这种做法的!
所以,我推荐在代码中,尽量避免类装饰器的使用,如果要在类中做一些操作,完全可以通过修改类的魔法方法,继承,元类等等方式来实现。如果避免不了,那也请谨慎处理。
来源:https://juejin.cn/post/7101493195212587039
猜你喜欢
- 一个asp读取数据库中数据到数组的类,仅供参考!DbPath = "test.mdb"’数据库位置&
- 总结调试网站获取cookies时请查看,r.header和r.request.header这两个属性,因为cookie说不准出现在他们俩谁里
- torch.Tensor有4种常见的乘法:*, torch.mul, torch.mm, torch.matmul. 本文抛砖引玉,简单叙述
- http://pyhdfs.readthedocs.io/en/latest/1:安装由于是windows环境(linux其实也一样),只要
- 前言引用一张比较清晰易懂的图php伪协议是php自己支持的一种协议与封装协议,简单来说就是php定义的一种特殊访问资源的方法。常见的php伪
- Python中的布尔类型Python中的布尔类型(bool)只有两个取值,分别是True和False。bool类型通常用于逻辑判断和条件控制
- 写在前面 最近和几个小伙伴们在写字节跳动第五届青训营后端组的大作业。虽然昨天已经提交了项目,但有很多地方值得总结一下,比如这一篇,
- 什么是品牌的视觉传达品牌,这个熟悉而又陌生的名词,有时总会让人产生误解。品牌很广,广到一个意会颇深的战略发展理念;品牌很小,小到一个清晰可见
- 在这可以用join()函数'x'.join(y),x可以是任意分割字符,y是列表或元组。以列表为例,可以将列表中的每一个元素
- 如下所示:# u [32,30,200]# u_logits [400,32,30]q_j_400 = [] for j in range(
- 本文实例为大家分享了js实现黑白div块画空心图形的具体代码,供大家参考,具体内容如下<!DOCTYPE html><ht
- 一般来说,我们判断 iframe 是否加载完成其实与 判断 JavaScript 文件是否加载完成 采用的方法很类似:var&nb
- 前言大家都知道Python 是一门强类型、动态类型检查的语言。所谓动态类型,是指在定义变量时,我们无需指定变量的类型,Python 解释器会
- 这里所说的“小偷”指的是在ASP中运用XML中的xmlhttp组件提供的强大功能,把远程网站上的数据(图片,网页及其他文件)抓取采集到本地,
- 前言这篇文章主要给大家总结了关于学习Python的新手们容易犯的几个错误,一共四个易犯错误,下面来看看详细的介绍吧。一、i+=1 不等于++
- 本文实例讲述了Python实现的简单计算器功能。分享给大家供大家参考,具体如下:使用python编写一款简易的计算器计算器效果图首先搭建计算
- 环境准备Python3.6pip install Django==2.0.1pip install celery==4.1.0pip ins
- 今天做项目时,有一个这样的需求,需要动态删除的Tab,比如:可以删除某一个,可以删除多个。每一个Tab对应一个iframe。本来我的代码是这
- javascript sort()排序用法sort() 方法用于对数组的元素进行排序,并返回数组。默认排序顺序是根据字符串UniCode码。
- 分页,就是按照某种规则显示分组数据集,但是在SQL Server 中,分页并不是十分容易就能够实现。在过去,开发人员通常需要自己编写程序,使