当前位置:

首页 > 编程开发 > 如何使用 Python 中的面向切面编程(AOP)和装饰器?

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

什么是AOPAOP,就是面向切面编程,简单的说,就是动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。这样我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。这种思想,可以使原有代码逻辑更清晰,对原有代码毫无入侵性,常用于像权限管理,日志记录,事物管理等等。而Python中的装饰器就是很著名的设计,常用于有切面需求的场景。类如,Django中就大量使用装饰器去

本文目录

    什么是 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

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

    类装饰器

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

    • 函数装饰函数

    • 类装饰函数

    • 函数装饰类

    • 类装饰类

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

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

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

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Python
    相关文章 更多
    python删除一个列表里的重复元素的方法与代码示例
    python删除一个列表里的重复元素的方法与代码示例

    学习如何使用Python高效删除列表中的重复元素。本文提供5种常见方法的代码示例,涵盖保持顺序与不保持顺序的场景,适合初学者和进阶开发者参考。

    Python写接口的步骤及完整代码示例
    Python写接口的步骤及完整代码示例

    详细讲解使用Python编写Web接口的核心步骤与完整代码示例,涵盖环境准备、路由定义、数据校验、服务启动及调试测试方法。

    codex怎么读取word文档及docx文件操作教程
    codex怎么读取word文档及docx文件操作教程

    介绍如何利用编程工具读取和解析Word文档(docx格式),涵盖基础文本提取代码示例、结构化数据处理逻辑,以及处理复杂排版时的技术局限与替代方案。

    怎么在Python中调用C语言代码提升性能
    怎么在Python中调用C语言代码提升性能

    详细讲解在Python中调用C语言代码提升性能的完整实践指南,包含原理分析、编译步骤、ctypes与CFFI具体实现、性能对比及常见陷阱。

    Python与C语言的性能差异及底层原理
    Python与C语言的性能差异及底层原理

    深入解析Python与C语言在执行效率上的数量级差异,从编译机制、变量内存布局、动态类型推断到GIL锁,系统阐明底层核心原理。

    Python和C语言哪个更适合编程入门
    Python和C语言哪个更适合编程入门

    客观对比Python与C语言在心智负担、底层机制与学习反馈上的核心差异,提供清晰有据的选择标准与切实的入门学习路径。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    langgraph python guide中文入门教程详解
    langgraph python guide中文入门教程详解

    本篇LangGraph Python中文入门教程详解,系统梳理StateGraph状态机定义、节点编写、边与条件路由、检查点持久化(MemorySaver)等核心机制,提供从环境安装到完整循环智能体构建的代码示例与实操指导。

    Windows 安装 Python:从官方入口到虚拟环境的可复现实践
    Windows 安装 Python:从官方入口到虚拟环境的可复现实践

    面向后端开发者,基于 Python 官方文档说明如何在 Windows 上安装 Python、创建 venv 虚拟环境及使用 pip 管理依赖。包含可复制命令、验证方法及工具选择边界,避免常见配置误区。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。