如何使用Python中的面向切面编程AOP和装饰器

什么是 AOP

aop,就是面向切面编程,简单的说,就是动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。这样我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。

这种思想,可以使原有代码逻辑更清晰,对原有代码毫无入侵性,常用于像权限管理,日志记录,事物管理等等。

Python 中的装饰器就是很著名的设计,常用于有切面需求的场景。

类如,Django 中就大量使用装饰器去完成一下切面需求,如权限控制,内容过滤,请求管理等等。

立即学习“Python免费学习笔记(深入)”;

装饰器

Python 装饰器(fuctional decorators)就是用于拓展原来函数功能的一种函数,目的是在不改变原函数名或类名的情况下,给函数增加新的功能。

下面就跟我一起详细的了解下装饰器是如何工作的。

首先,要明确一个概念:Python 中万物皆对象,函数也是是对象!

所以,一个函数作为对象,可以在另一个函数中定义。看下面示例:

def a():    def b():        print("I'm b")    b()    c = b    return cd = 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 ba = c(a)a()

登录后复制

输出结果:

在函数执行前,做一些操作
I’m a
在函数执行后,做一些操作

如上,我们可以将函数再包一层,将新的函数 b,作为对象返回。这样通过函数 c,将 a 改变并重新赋值给 a,这样就实现了改变函数 a,并同样使用函数 a 来调用。

但是这样写起来非常不方便,因为需要重新赋值,所以在 Python 中,可以通过 @ 来实现,将函数作为参数传递。

看下示例:

def c(func):    def b():        print('在函数执行前,做一些操作')        func()        print("在函数执行后,做一些操作")    return b@cdef 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@cdef 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@cdef 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@cdef a(name, age):    print('函数执行中。。。')    return "我是 {}, 今年{}岁 ".format(name, age)@cdef 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@cdef a(name, age):    print('函数执行中。。。')    return "我是 {}, 今年{}岁 ".format(name, age)print(a.__name__)

登录后复制

输出结果:

b

会发现函数实际上是函数 b 了,这就有问题了,那么该怎么解决呢,有人就会想到,可以在装饰器函数中先把原函数的元数据保存下来,在最后再讲 b 函数的元数据改为原函数的,再返回 b。这样的确是可以的!但我们不这样用,为什么?

因为 Python 早就想到这个问题啦,所以给我们提供了一个内置的方法,来自动实现原数据的保存和替换工作。哈哈,这样就不同我们自己动手啦!

看下面示例:

from functools import wrapsdef c(func):    @wraps(func)    def b(*args, **kwargs):        print('在函数执行前,做一些操作')        result = func(*args, **kwargs)        print("在函数执行后,做一些操作")        return result    return b@cdef a(name, age):    print('函数执行中。。。')    return "我是 {}, 今年{}岁 ".format(name, age)print(a.__name__)

登录后复制

输出结果:

a

使用内置的 wraps 装饰器,将原函数作为装饰器参数,实现函数原数据的保留替换功能。

耶!装饰器还可以带参数啊,你看上面 wraps 装饰器就传入了参数。哈哈,是的,装饰器还可以带参数,那怎么实现呢?

看下面示例:

from functools import wrapsdef 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 wrapsdef 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 cdef 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@bdef a():    pass

登录后复制

Python面向切面编程AOP及装饰器怎么使用

类装饰器

在函数装饰器方面,很多人搞不清楚,是因为装饰器可以用函数实现(像上面),也可以用类实现。因为函数和类都是对象,同样可以作为被装饰的对象,所以根据被装饰的对象不同,一同有下面四种情况:

函数装饰函数

类装饰函数

函数装饰类

类装饰类

下面我们依次来说明一下这四种情况的使用。

1、函数装饰函数

from functools import wrapsdef 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 wrapsclass 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 wrapsclass 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 wrapsdef 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 的属性,但是类的元数据无法改变。很多内置的方法也就会有问题。我个人是不推荐这种做法的!

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

以上就是如何使用Python中的面向切面编程AOP和装饰器的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2236883.html

(0)
上一篇 2025年2月26日 18:41:46
下一篇 2025年2月26日 18:42:12

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 如何设置日志记录策略以满足需求

    构建完善的日志记录策略并非易事,需要周全考虑诸多因素,例如日志用途、数据量、存储空间以及安全性等等。 以下步骤和建议将帮助您创建高效的日志记录策略: 一、明确日志记录目标 首先,必须明确日志记录的最终目的: 问题排查: 记录系统或应用错误和…

    2025年3月28日
    100
  • JS日志分析工具有哪些推荐

    在javascript日志分析领域,有多款优秀的工具可以帮助开发者进行日志的记录、查看和分析。以下是一些推荐的js日志分析工具: Kotaemon: 特点:专为开发者和运维人员设计,通过直观的界面、灵活的过滤和强大的分析功能,帮助用户快速定…

    互联网 2025年3月28日
    100
  • Linux FTP服务器如何配置SSL加密

    本文将指导您如何在Linux系统上为FTP服务器配置SSL加密,以增强数据传输安全性。我们将使用vsftpd (Very Secure FTP Daemon) 作为示例。 第一步:安装vsftpd和获取SSL证书 首先,安装vsftpd: …

    2025年3月28日
    100
  • Linux strings输出格式能自定义吗

    strings 命令用于从二进制文件中提取可打印的字符串。默认情况下,strings 命令的输出格式是固定的,即每行显示一个字符串,前面带有其在文件中的字节偏移量。 虽然 strings 命令本身不提供直接的自定义输出格式选项,但你可以通过…

    互联网 2025年3月28日
    100
  • 成品网站python在线观看有哪些?

    本文推荐十个学习Python3的优质资源,涵盖国内外知名网站和平台。包括菜鸟教程、廖雪峰Python3教程、Python官方文档等内容详尽、权威可靠的学习资源,以及实验楼、Codecademy等注重实践和项目学习的平台,还有Python12…

    2025年3月28日
    100
  • Python数据库操作:必须使用ORM吗?

    Python数据库操作:ORM并非唯一选择 许多Python开发者习惯使用ORM(对象关系映射)工具,例如SQLAlchemy,来操作数据库。 但一个常见问题是:是否必须为每个数据库表都创建对应的ORM映射?尤其面对大量表时,这种方法显得冗…

    2025年3月28日
    100
  • mySQL下载完安装不了

    mysql安装失败的原因主要有:1. 权限问题,需以管理员身份运行或使用sudo命令;2. 依赖项缺失,需安装相关开发包;3. 端口冲突,需关闭占用3306端口的程序或修改配置文件;4. 安装包损坏,需重新下载并验证完整性;5. 环境变量配…

    2025年3月28日
    100
  • 币安Alpha是什么

    安币Alpha是币安平台上为专业交易者和投资者提供的工具和服务聚合平台。它的核心功能包括:1.策略广场,汇集不同交易策略;2.策略构建器,允许自定义交易策略;3.高级数据分析,提供市场分析工具;4.机构级服务,满足专业投资者的需求。 币安作…

    2025年3月28日
    100
  • python写火车票抢票软件下载

    可以下载 Python 火车票抢票软件来解决抢票难题,最常用的软件是 12306 抢票助手。下载步骤: 1. 访问 GitHub 项目。2. 下载 ZIP 文件。3. 解压文件。使用步骤: 1. 运行“run.py”。2. 输入账号密码。3…

    2025年3月14日
    200
  • 小红书水印怎么批量去掉

    小红书水印批量去除方法:使用在线工具(水印云、智图、Apowersoft)使用第三方软件(VideoProc、VSDC、FonePaw)使用脚本(需了解 Python 或 JavaScript) 小红书水印如何批量去除? 批量去除小红书水印…

    2025年3月14日
    200

发表回复

登录后才能评论