首頁 >後端開發 >Python教學 >如何使用Python中的面向切面編程AOP和裝飾器

如何使用Python中的面向切面編程AOP和裝飾器

王林
王林轉載
2023-05-10 08:13:071378瀏覽

什麼是 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 被 bcd 三个装饰器装饰时,执行顺序如下图所示,多个同理。

@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__)
# 输出结果:
<class &#39;function&#39;>

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

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(&#39;装饰器传入参数为:{}&#39;.format(self._name))
            print(&#39;在函数执行前,做一些操作&#39;)
            result = func(*args, **kwargs)
            print("在函数执行后,做一些操作")
            return result
        return wrapper
@D(name=&#39;我是装饰器参数&#39;)
def a(name, age):
    print(&#39;函数执行中。。。&#39;)
    return "我是 {}, 今年{}岁 ".format(name, age)
print(a.__class__)
# 输出结果:
<class &#39;function&#39;>

以上所示,只是将用函数定义的装饰器改为使用类来实现而已。还是用于装饰函数,因为在类的 __call__ 中,最后返回的还是一个函数。

此为带装饰器参数的装饰器实现方法,是通过 __call__ 方法。

若装饰器不带参数,则可以将 __init__ 方法去掉,但是在使用装饰器时,需要 @D() 这样使用,如下:

from functools import wraps
class D(object):
    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(&#39;在函数执行前,做一些操作&#39;)
            result = func(*args, **kwargs)
            print("在函数执行后,做一些操作")
            return result
        return wrapper
@D()
def a(name, age):
    print(&#39;函数执行中。。。&#39;)
    return "我是 {}, 今年{}岁 ".format(name, age)

print(a.__class__)
# 输出结果:
<class &#39;function&#39;>

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

3、函数装饰类

下面重点来啦,我们常见的装饰器都是用于装饰函数的,返回的对象也是一个函数,而要装饰类,那么返回的对象就要是类,且类的元数据等也要保留。

不怕丢脸的说,目前我还不知道怎么实现完美的类装饰器,在装饰类的时候,一般有两种方法:

返回一个函数,实现类在创建实例的前后执行操作,并正常返回此类的实例。但是这样经过装饰器的类就属于函数了,其无法继承,但可以正常调用创建实例。

如下:

from functools import wraps
def d(name):
    def c(cls):
        @wraps(cls)
        def b(*args, **kwargs):
            print(&#39;装饰器传入参数为:{}&#39;.format(name))
            print(&#39;在类初始化前,做一些操作&#39;)
            instance = cls(*args, **kwargs)
            print("在类初始化后,做一些操作")
            return instance
        return b
    return c
@d(name=&#39;我是装饰器参数&#39;)
class A(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print(&#39;类初始化实例,{} {}&#39;.format(self.name, self.age))

a = A(&#39;Amos&#39;, 24)
print(a.__class__)
print(A.__class__)

# 输出结果:
装饰器传入参数为:我是装饰器参数
在类初始化前,做一些操作
类初始化实例,Amos 24
在类初始化后,做一些操作
<class &#39;__main__.A&#39;>
<class &#39;function&#39;>

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

4、类装饰类

接上文,返回一个类,实现类在创建实例的前后执行操作,但类已经改变了,创建的实例也已经不是原本类的实例了。

看下面示例:

def desc(name):
    def decorator(aClass):
        class Wrapper(object):
            def __init__(self, *args, **kwargs):
                print(&#39;装饰器传入参数为:{}&#39;.format(name))
                print(&#39;在类初始化前,做一些操作&#39;)
                self.wrapped = aClass(*args, **kwargs)
                print("在类初始化后,做一些操作")

            def __getattr__(self, name):
                print(&#39;Getting the {} of {}&#39;.format(name, self.wrapped))
                return getattr(self.wrapped, name)

            def __setattr__(self, key, value):
                if key == &#39;wrapped&#39;:  # 这里捕捉对wrapped的赋值
                    self.__dict__[key] = value
                else:
                    setattr(self.wrapped, key, value)

        return Wrapper
    return decorator
@desc(name=&#39;我是装饰器参数&#39;)
class A(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        print(&#39;类初始化实例,{} {}&#39;.format(self.name, self.age))

a = A(&#39;Amos&#39;, 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和裝飾器的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除