网易首页 > 网易号 > 正文 申请入驻

Python 面向切面编程 AOP 和装饰器

0
分享至

  作者:王阳旭

  来源:https://amos-x.com/index.php/amos/archives/python-aop-decorator

  
什么是 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中定义和赋值的函数bc在外部作用域是无法调用的。

  这意味着一个功能可以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))

  # 输出结果:
装饰器传入参数为:我是装饰器e
我是装饰器e: 在函数执行前,做一些操作

  装饰器传入参数为:我是装饰器d
我是装饰器d: 在函数执行前,做一些操作
函数执行中。。。
我是装饰器d: 在函数执行后,做一些操作

  我是装饰器e: 在函数执行后,做一些操作
我是 Amos, 今年24岁

  如上所示,当两个装饰器同时使用时,可以想象成洋葱,最下层的装饰器先包装一层,然后一直到最上层装饰器,完成多层的包装。然后执行时,就像切洋葱,从最外层开始,只执行到被装饰函数运行时,就到了下一层,下一层又执行到函数运行时到下一层,一直到执行了被装饰函数后,就像切到了洋葱的中间,然后再往下,依次从最内层开始,依次执行到最外层。

  示例:

  当一个函数abcd三个装饰器装饰时,执行顺序如下图所示,多个同理。

  @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__)

  # 输出结果:

  此为最常见的装饰器,用于装饰函数,返回的是一个函数。

  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__)

  # 输出结果:

  以上所示,只是将用函数定义的装饰器改为使用类来实现而已。还是用于装饰函数,因为在类的__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__)

  # 输出结果:

  如上是比较方便简答的,使用类定义函数装饰器,且返回对象为函数,元数据保留。

  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
在类初始化后,做一些操作

  如上所示,就是第一种方法。

  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
在类初始化后,做一些操作
.decorator..Wrapper'>
Wrapper

  如上,看到了吗,通过在函数中新定义类,并返回类,这样函数还是类,但是经过装饰器后,类A已经变成了类Wrapper,且生成的实例a也是类Wrapper的实例,即使通过__getattr____setattr__两个方法,使得实例a的属性都是在由类A创建的实例wrapped的属性,但是类的元数据无法改变。很多内置的方法也就会有问题。

  我个人是不推荐这种做法的!

  所以,我推荐在代码中,尽量避免类装饰器的使用,如果要在类中做一些操作,完全可以通过修改类的魔法方法,继承,元类等等方式来实现。如果避免不了,那也请谨慎处理。

  如果各位有更好的方式能够完美实现类装饰器,欢迎留言交流。

  OK,到此基本就将装饰器都说完了,理解了其原理,也就可以更好的灵活使用。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
曝小红书17亿拿下世界杯版权分销!咪咕花了10多亿 央视已赚30亿

曝小红书17亿拿下世界杯版权分销!咪咕花了10多亿 央视已赚30亿

念洲
2026-05-26 17:00:37
四大民调出炉,45%支持赖清德连任,但接下来这一幕打脸赖清德

四大民调出炉,45%支持赖清德连任,但接下来这一幕打脸赖清德

真的好爱你
2026-05-28 19:19:43
海参崴兄弟俩被授予“乌克兰英雄”!战场坚守至最后一刻

海参崴兄弟俩被授予“乌克兰英雄”!战场坚守至最后一刻

项鹏飞
2026-05-26 18:28:15
撒贝宁胡杏儿现身广东龙船,一个干划一个脚朝天,恶心一幕出现了

撒贝宁胡杏儿现身广东龙船,一个干划一个脚朝天,恶心一幕出现了

笑饮孤鸿非
2026-05-29 00:10:21
荷兰军舰闯西沙,被解放军电子战“致盲”,回家都找不到北了!

荷兰军舰闯西沙,被解放军电子战“致盲”,回家都找不到北了!

荷兰豆爱健康
2026-05-28 13:19:58
铁证面前,还能撤案?深扒无果、信息全封,路虎车主背景有多硬?

铁证面前,还能撤案?深扒无果、信息全封,路虎车主背景有多硬?

世界圈
2026-03-24 12:52:50
76岁的万科创始人王石,最近彻底成了全网焦点。

76岁的万科创始人王石,最近彻底成了全网焦点。

梦录的西方史话
2026-04-23 14:36:39
世界第一法网爆冷出局!24岁网球天王赛中晕眩求救,18分连丢太诡异

世界第一法网爆冷出局!24岁网球天王赛中晕眩求救,18分连丢太诡异

时光慢旅人
2026-05-29 01:36:26
在日华人由衷感慨:别信媒体吹牛,日本如今仅相当于我国二线城市

在日华人由衷感慨:别信媒体吹牛,日本如今仅相当于我国二线城市

今墨缘
2026-05-19 20:29:18
看了袁泉的装扮,悟了:穿搭不花哨、头发不烫卷,教科书级别的美

看了袁泉的装扮,悟了:穿搭不花哨、头发不烫卷,教科书级别的美

明星私服穿搭daily
2026-05-28 14:03:43
深大迎来90后副教授,今年29岁,本人不仅长得漂亮,履历更不一般

深大迎来90后副教授,今年29岁,本人不仅长得漂亮,履历更不一般

凯旋学长
2026-05-28 17:14:19
广东一家制衣厂仅有一位男员工,百余名女同事围着轮流投喂呵护

广东一家制衣厂仅有一位男员工,百余名女同事围着轮流投喂呵护

捣蛋窝
2026-04-07 13:22:20
五千万啃光!马蓉从阔太沦落澳洲超市夜班,出门捂脸不敢见人

五千万啃光!马蓉从阔太沦落澳洲超市夜班,出门捂脸不敢见人

皮蛋儿电影
2026-05-14 09:59:14
北京亮马河国际风情水岸入选国际城市更新全球十大杰出实践

北京亮马河国际风情水岸入选国际城市更新全球十大杰出实践

中国青年报
2026-05-28 15:36:29
5月下旬,贵人悄然入局,事业迎来关键转机的三个星座,把握当下

5月下旬,贵人悄然入局,事业迎来关键转机的三个星座,把握当下

小晴星座说
2026-05-24 18:53:38
终于看懂胡三元!他根本不是爱花彩香,心里一辈子装的都是李青娥

终于看懂胡三元!他根本不是爱花彩香,心里一辈子装的都是李青娥

东方不败然多多
2026-05-27 16:42:51
巴萨为何选择戈登?跑动积极有冲击力 欧冠表现亮眼

巴萨为何选择戈登?跑动积极有冲击力 欧冠表现亮眼

球事百科吖
2026-05-29 04:34:47
河南南阳沪陕高速发生客车追尾重大交通事故,13人遇难

河南南阳沪陕高速发生客车追尾重大交通事故,13人遇难

天空空啊
2026-05-28 18:53:34
“娇小型女王”!引退7年仍是天花板,她才是真正被低估的宝藏!

“娇小型女王”!引退7年仍是天花板,她才是真正被低估的宝藏!

管鲍老四级
2026-05-27 16:53:21
“农村父母就是这样被骗的”,中职女孩穿廉价警服,毕业就傻眼了

“农村父母就是这样被骗的”,中职女孩穿廉价警服,毕业就傻眼了

妍妍教育日记
2026-05-11 18:59:23
2026-05-29 04:55:00
Python猫 incentive-icons
Python猫
人生苦短,我用Python。博客:https://pythoncat.top
729文章数 8120关注度
往期回顾 全部

科技要闻

利润跌27%:快手只剩“可灵”这张牌?

头条要闻

男子疑遭家暴跳楼身亡 母亲:儿媳说"你不配活在世上"

头条要闻

男子疑遭家暴跳楼身亡 母亲:儿媳说"你不配活在世上"

体育要闻

唐斯经历的一切,此刻的他与尼克斯

娱乐要闻

林俊杰七七与大哥嫂子的瓜剪不断理还乱

财经要闻

小米仍需一次创业

汽车要闻

从智驾兜底到自研4nm芯片,再到迪迪虾,比亚迪智能化战略凭什么封神?

态度原创

房产
健康
本地
亲子
公开课

房产要闻

突发重磅!三亚新机场公司正式成立!

专家教你辨认“正规外泌体”!

本地新闻

用剪纸的方式,打开江苏扬州

亲子要闻

《灸童说:中医药成语故事》悬壶济世

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版