您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“Python面向切面編程AOP及裝飾器怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“Python面向切面編程AOP及裝飾器怎么使用”吧!
AOP,就是面向切面編程,簡單的說,就是動態(tài)地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程。
我們管切入到指定類指定方法的代碼片段稱為切面,而切入到哪些類、哪些方法則叫切入點。這樣我們就可以把幾個類共有的代碼,抽取到一個切片中,等到需要時再切入對象中去,從而改變其原有的行為。
這種思想,可以使原有代碼邏輯更清晰,對原有代碼毫無入侵性,常用于像權(quán)限管理,日志記錄,事物管理等等。
而 Python 中的裝飾器就是很著名的設(shè)計,常用于有切面需求的場景。
類如,Django 中就大量使用裝飾器去完成一下切面需求,如權(quán)限控制,內(nèi)容過濾,請求管理等等。
Python 裝飾器(fuctional decorators)就是用于拓展原來函數(shù)功能的一種函數(shù),目的是在不改變原函數(shù)名或類名的情況下,給函數(shù)增加新的功能。
下面就跟我一起詳細的了解下裝飾器是如何工作的。
首先,要明確一個概念:Python 中萬物皆對象,函數(shù)也是是對象!
所以,一個函數(shù)作為對象,可以在另一個函數(shù)中定義??聪旅媸纠?/p>
def a(): def b(): print("I'm b") b() c = b return c d = a() d() b() c()
輸出結(jié)果為:
I'm b
I'm b
拋出 NameError: name 'b' is not defined 錯誤
拋出 NameError: name 'c' is not defined 錯誤
從上可以看出,由于函數(shù)是對象,所以:
可以賦值給變量
可以在另一個函數(shù)中定義
然后,return
可以返回一個函數(shù)對象。這個函數(shù)對象是在另一個函數(shù)中定義的。由于作用域不同,所以只有 return
返回的函數(shù)可以調(diào)用,在函數(shù) a
中定義和賦值的函數(shù) b
和 c
在外部作用域是無法調(diào)用的。
這意味著一個功能可以 return
另一個功能。
除了可以作為對象返回外,函數(shù)對象還可以作為參數(shù)傳遞給另一個函數(shù):
def a(): print("I'm a") def b(func): print("I'm b") func() b(a)
輸出結(jié)果:
I'm b
I'm a
OK,現(xiàn)在,基于函數(shù)的這些特性,我們就可以創(chuàng)建一個裝飾器,用來在不改變原函數(shù)的情況下,實現(xiàn)功能。
比如,我們要在函數(shù)執(zhí)行前和執(zhí)行后分別執(zhí)行一些別的操作,那么根據(jù)上面函數(shù)可以作為參數(shù)傳遞,我們可以這樣實現(xiàn),看下面示例:
def a(): print("I'm a") def b(func): print('在函數(shù)執(zhí)行前,做一些操作') func() print("在函數(shù)執(zhí)行后,做一些操作") b(a)
輸出結(jié)果:
在函數(shù)執(zhí)行前,做一些操作
I'm a
在函數(shù)執(zhí)行后,做一些操作
但是這樣的話,原函數(shù)就變成了另一個函數(shù),每加一個功能,就要在外面包一層新的函數(shù),這樣原來調(diào)用的地方,就會需要全部修改,這明顯不方便,就會想,有沒有辦法可以讓函數(shù)的操作改變,但是名稱不改變,還是作為原函數(shù)呢。
看下面示例:
def a(): print("I'm a") def c(func): def b(): print('在函數(shù)執(zhí)行前,做一些操作') func() print("在函數(shù)執(zhí)行后,做一些操作") return b a = c(a) a()
輸出結(jié)果:
在函數(shù)執(zhí)行前,做一些操作
I'm a
在函數(shù)執(zhí)行后,做一些操作
如上,我們可以將函數(shù)再包一層,將新的函數(shù) b
,作為對象返回。這樣通過函數(shù) c
,將 a
改變并重新賦值給 a
,這樣就實現(xiàn)了改變函數(shù) a
,并同樣使用函數(shù) a
來調(diào)用。
但是這樣寫起來非常不方便,因為需要重新賦值,所以在 Python 中,可以通過 @
來實現(xiàn),將函數(shù)作為參數(shù)傳遞。
看下示例:
def c(func): def b(): print('在函數(shù)執(zhí)行前,做一些操作') func() print("在函數(shù)執(zhí)行后,做一些操作") return b @c def a(): print("I'm a") a()
輸出結(jié)果:
在函數(shù)執(zhí)行前,做一些操作
I'm a
在函數(shù)執(zhí)行后,做一些操作
如上,通過 @c
,就實現(xiàn)了將函數(shù) a
作為參數(shù),傳入 c
,并將返回的函數(shù)重新作為函數(shù) a
。這 c
也就是一個簡單的函數(shù)裝飾器。
且如果函數(shù)是有返回值的,那么改變后的函數(shù)也需要有返回值,如下所以:
def c(func): def b(): print('在函數(shù)執(zhí)行前,做一些操作') result = func() print("在函數(shù)執(zhí)行后,做一些操作") return result return b @c def a(): print("函數(shù)執(zhí)行中。。。") return "I'm a" print(a())
輸出結(jié)果:
在函數(shù)執(zhí)行前,做一些操作
函數(shù)執(zhí)行中。。。
在函數(shù)執(zhí)行后,做一些操作
I'm a
如上所示:通過將返回值進行傳遞,就可以實現(xiàn)函數(shù)執(zhí)行前后的操作。但是你會發(fā)現(xiàn)一個問題,就是為什么輸出 I'm a
會在最后才打印出來?
因為 I'm a
是返回的結(jié)果,而實際上函數(shù)是 print("在函數(shù)執(zhí)行后,做一些操作")
這一操作前運行的,只是先將返回的結(jié)果給到了 result
,然后 result
傳遞出來,最后由最下方的 print(a())
打印了出來。
那如何函數(shù) a
帶參數(shù)怎么辦呢?很簡單,函數(shù) a
帶參數(shù),那么我們返回的函數(shù)也同樣要帶參數(shù)就好啦。
看下面示例:
def c(func): def b(name, age): print('在函數(shù)執(zhí)行前,做一些操作') result = func(name, age) print("在函數(shù)執(zhí)行后,做一些操作") return result return b @c def a(name, age): print("函數(shù)執(zhí)行中。。。") return "我是 {}, 今年{}歲 ".format(name, age) print(a('Amos', 24))
輸出結(jié)果:
在函數(shù)執(zhí)行前,做一些操作
函數(shù)執(zhí)行中。。。
在函數(shù)執(zhí)行后,做一些操作
我是 Amos, 今年24歲
但是又有問題了,我寫一個裝飾器 c
,需要裝飾多個不同的函數(shù),這些函數(shù)的參數(shù)各不相同,那么怎么辦呢?簡單,用 *args
和 **kwargs
來表示所有參數(shù)即可。
如下示例:
def c(func): def b(*args, **kwargs): print('在函數(shù)執(zhí)行前,做一些操作') result = func(*args, **kwargs) print("在函數(shù)執(zhí)行后,做一些操作") return result return b @c def a(name, age): print('函數(shù)執(zhí)行中。。。') return "我是 {}, 今年{}歲 ".format(name, age) @c def d(sex, height): print('函數(shù)執(zhí)行中。。。') return '性別:{},身高:{}'.format(sex, height) print(a('Amos', 24)) print(d('男', 175))
輸出結(jié)果:
在函數(shù)執(zhí)行前,做一些操作
函數(shù)執(zhí)行中。。。
在函數(shù)執(zhí)行后,做一些操作
我是 Amos, 今年24歲在函數(shù)執(zhí)行前,做一些操作
函數(shù)執(zhí)行中。。。
在函數(shù)執(zhí)行后,做一些操作
性別:男,身高:175
如上就解決了參數(shù)的問題,哇,這么好用。那是不是這樣就沒有問題了?并不是!經(jīng)過裝飾器裝飾后的函數(shù),實際上已經(jīng)變成了裝飾器函數(shù) c
中定義的函數(shù) b
,所以函數(shù)的元數(shù)據(jù)則全部改變了!
如下示例:
def c(func): def b(*args, **kwargs): print('在函數(shù)執(zhí)行前,做一些操作') result = func(*args, **kwargs) print("在函數(shù)執(zhí)行后,做一些操作") return result return b @c def a(name, age): print('函數(shù)執(zhí)行中。。。') return "我是 {}, 今年{}歲 ".format(name, age) print(a.__name__)
輸出結(jié)果:
b
會發(fā)現(xiàn)函數(shù)實際上是函數(shù) b
了,這就有問題了,那么該怎么解決呢,有人就會想到,可以在裝飾器函數(shù)中先把原函數(shù)的元數(shù)據(jù)保存下來,在最后再講 b
函數(shù)的元數(shù)據(jù)改為原函數(shù)的,再返回 b
。這樣的確是可以的!但我們不這樣用,為什么?
因為 Python 早就想到這個問題啦,所以給我們提供了一個內(nèi)置的方法,來自動實現(xiàn)原數(shù)據(jù)的保存和替換工作。哈哈,這樣就不同我們自己動手啦!
看下面示例:
from functools import wraps def c(func): @wraps(func) def b(*args, **kwargs): print('在函數(shù)執(zhí)行前,做一些操作') result = func(*args, **kwargs) print("在函數(shù)執(zhí)行后,做一些操作") return result return b @c def a(name, age): print('函數(shù)執(zhí)行中。。。') return "我是 {}, 今年{}歲 ".format(name, age) print(a.__name__)
輸出結(jié)果:
a
使用內(nèi)置的 wraps
裝飾器,將原函數(shù)作為裝飾器參數(shù),實現(xiàn)函數(shù)原數(shù)據(jù)的保留替換功能。
耶!裝飾器還可以帶參數(shù)啊,你看上面 wraps
裝飾器就傳入了參數(shù)。哈哈,是的,裝飾器還可以帶參數(shù),那怎么實現(xiàn)呢?
看下面示例:
from functools import wraps def d(name): def c(func): @wraps(func) def b(*args, **kwargs): print('裝飾器傳入?yún)?shù)為:{}'.format(name)) print('在函數(shù)執(zhí)行前,做一些操作') result = func(*args, **kwargs) print("在函數(shù)執(zhí)行后,做一些操作") return result return b return c @d(name='我是裝飾器參數(shù)') def a(name, age): print('函數(shù)執(zhí)行中。。。') return "我是 {}, 今年{}歲 ".format(name, age) print(a('Amos', 24))
輸出結(jié)果:
裝飾器傳入?yún)?shù)為:我是裝飾器參數(shù)
在函數(shù)執(zhí)行前,做一些操作
函數(shù)執(zhí)行中。。。
在函數(shù)執(zhí)行后,做一些操作
我是 Amos, 今年24歲
如上所示,很簡單,只需要在原本的裝飾器之上,再包一層,相當(dāng)于先接收裝飾器參數(shù),然后返回一個不帶參數(shù)的裝飾器,然后再將函數(shù)傳入,最后返回變化后的函數(shù)。
這樣就可以實現(xiàn)很多功能了,這樣可以根據(jù)傳給裝飾器的參數(shù)不同,來分別實現(xiàn)不同的功能。
另外,可能會有人問, 可以在同一個函數(shù)上,使用多個裝飾器嗎?答案是:可以!
看下面示例:
from functools import wraps def d(name): def c(func): @wraps(func) def b(*args, **kwargs): print('裝飾器傳入?yún)?shù)為:{}'.format(name)) print('我是裝飾器d: 在函數(shù)執(zhí)行前,做一些操作') result = func(*args, **kwargs) print("我是裝飾器d: 在函數(shù)執(zhí)行后,做一些操作") return result return b return c def e(name): def c(func): @wraps(func) def b(*args, **kwargs): print('裝飾器傳入?yún)?shù)為:{}'.format(name)) print('我是裝飾器e: 在函數(shù)執(zhí)行前,做一些操作') result = func(*args, **kwargs) print("我是裝飾器e: 在函數(shù)執(zhí)行后,做一些操作") return result return b return c @e(name='我是裝飾器e') @d(name='我是裝飾器d') def func_a(name, age): print('函數(shù)執(zhí)行中。。。') return "我是 {}, 今年{}歲 ".format(name, age) print(func_a('Amos', 24)) 行后,做一些操作 我是 Amos, 今年24歲
輸出結(jié)果:
裝飾器傳入?yún)?shù)為:我是裝飾器e
我是裝飾器e: 在函數(shù)執(zhí)行前,做一些操作裝飾器傳入?yún)?shù)為:我是裝飾器d
我是裝飾器d: 在函數(shù)執(zhí)行前,做一些操作
函數(shù)執(zhí)行中。。。
我是裝飾器d: 在函數(shù)執(zhí)行后,做一些操作我是裝飾器e: 在函數(shù)執(zhí)
如上所示,當(dāng)兩個裝飾器同時使用時,可以想象成洋蔥,最下層的裝飾器先包裝一層,然后一直到最上層裝飾器,完成多層的包裝。然后執(zhí)行時,就像切洋蔥,從最外層開始,只執(zhí)行到被裝飾函數(shù)運行時,就到了下一層,下一層又執(zhí)行到函數(shù)運行時到下一層,一直到執(zhí)行了被裝飾函數(shù)后,就像切到了洋蔥的中間,然后再往下,依次從最內(nèi)層開始,依次執(zhí)行到最外層。
示例:
當(dāng)一個函數(shù) a
被 b
,c
,d
三個裝飾器裝飾時,執(zhí)行順序如下圖所示,多個同理。
@d @c @b def a(): pass
在函數(shù)裝飾器方面,很多人搞不清楚,是因為裝飾器可以用函數(shù)實現(xiàn)(像上面),也可以用類實現(xiàn)。因為函數(shù)和類都是對象,同樣可以作為被裝飾的對象,所以根據(jù)被裝飾的對象不同,一同有下面四種情況:
函數(shù)裝飾函數(shù)
類裝飾函數(shù)
函數(shù)裝飾類
類裝飾類
下面我們依次來說明一下這四種情況的使用。
from functools import wraps def d(name): def c(func): @wraps(func) def b(*args, **kwargs): print('裝飾器傳入?yún)?shù)為:{}'.format(name)) print('在函數(shù)執(zhí)行前,做一些操作') result = func(*args, **kwargs) print("在函數(shù)執(zhí)行后,做一些操作") return result return b return c @d(name='我是裝飾器參數(shù)') def a(name, age): print('函數(shù)執(zhí)行中。。。') return "我是 {}, 今年{}歲 ".format(name, age) print(a.__class__) # 輸出結(jié)果: <class 'function'>
此為最常見的裝飾器,用于裝飾函數(shù),返回的是一個函數(shù)。
也就是通過類來實現(xiàn)裝飾器的功能而已。通過類的 __call__
方法實現(xiàn):
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('裝飾器傳入?yún)?shù)為:{}'.format(self._name)) print('在函數(shù)執(zhí)行前,做一些操作') result = func(*args, **kwargs) print("在函數(shù)執(zhí)行后,做一些操作") return result return wrapper @D(name='我是裝飾器參數(shù)') def a(name, age): print('函數(shù)執(zhí)行中。。。') return "我是 {}, 今年{}歲 ".format(name, age) print(a.__class__) # 輸出結(jié)果: <class 'function'>
以上所示,只是將用函數(shù)定義的裝飾器改為使用類來實現(xiàn)而已。還是用于裝飾函數(shù),因為在類的 __call__
中,最后返回的還是一個函數(shù)。
此為帶裝飾器參數(shù)的裝飾器實現(xiàn)方法,是通過 __call__
方法。
若裝飾器不帶參數(shù),則可以將 __init__
方法去掉,但是在使用裝飾器時,需要 @D()
這樣使用,如下:
from functools import wraps class D(object): def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): print('在函數(shù)執(zhí)行前,做一些操作') result = func(*args, **kwargs) print("在函數(shù)執(zhí)行后,做一些操作") return result return wrapper @D() def a(name, age): print('函數(shù)執(zhí)行中。。。') return "我是 {}, 今年{}歲 ".format(name, age) print(a.__class__) # 輸出結(jié)果: <class 'function'>
如上是比較方便簡答的,使用類定義函數(shù)裝飾器,且返回對象為函數(shù),元數(shù)據(jù)保留。
下面重點來啦,我們常見的裝飾器都是用于裝飾函數(shù)的,返回的對象也是一個函數(shù),而要裝飾類,那么返回的對象就要是類,且類的元數(shù)據(jù)等也要保留。
不怕丟臉的說,目前我還不知道怎么實現(xiàn)完美的類裝飾器,在裝飾類的時候,一般有兩種方法:
返回一個函數(shù),實現(xiàn)類在創(chuàng)建實例的前后執(zhí)行操作,并正常返回此類的實例。但是這樣經(jīng)過裝飾器的類就屬于函數(shù)了,其無法繼承,但可以正常調(diào)用創(chuàng)建實例。
如下:
from functools import wraps def d(name): def c(cls): @wraps(cls) def b(*args, **kwargs): print('裝飾器傳入?yún)?shù)為:{}'.format(name)) print('在類初始化前,做一些操作') instance = cls(*args, **kwargs) print("在類初始化后,做一些操作") return instance return b return c @d(name='我是裝飾器參數(shù)') 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__) # 輸出結(jié)果: 裝飾器傳入?yún)?shù)為:我是裝飾器參數(shù) 在類初始化前,做一些操作 類初始化實例,Amos 24 在類初始化后,做一些操作 <class '__main__.A'> <class 'function'>
如上所示,就是第一種方法。
接上文,返回一個類,實現(xiàn)類在創(chuàng)建實例的前后執(zhí)行操作,但類已經(jīng)改變了,創(chuàng)建的實例也已經(jīng)不是原本類的實例了。
看下面示例:
def desc(name): def decorator(aClass): class Wrapper(object): def __init__(self, *args, **kwargs): print('裝飾器傳入?yún)?shù)為:{}'.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='我是裝飾器參數(shù)') 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__)
輸出結(jié)果:
裝飾器傳入?yún)?shù)為:我是裝飾器參數(shù)
在類初始化前,做一些操作
類初始化實例,Amos 24
在類初始化后,做一些操作
<class '__main__.desc.<locals>.decorator.<locals>.Wrapper'>
<class 'type'>
Wrapper
如上,看到了嗎,通過在函數(shù)中新定義類,并返回類,這樣函數(shù)還是類,但是經(jīng)過裝飾器后,類 A
已經(jīng)變成了類 Wrapper
,且生成的實例 a
也是類 Wrapper
的實例,即使通過 __getattr__
和 __setattr__
兩個方法,使得實例a的屬性都是在由類 A
創(chuàng)建的實例 wrapped
的屬性,但是類的元數(shù)據(jù)無法改變。很多內(nèi)置的方法也就會有問題。我個人是不推薦這種做法的!
所以,我推薦在代碼中,盡量避免類裝飾器的使用,如果要在類中做一些操作,完全可以通過修改類的魔法方法,繼承,元類等等方式來實現(xiàn)。如果避免不了,那也請謹(jǐn)慎處理。
到此,相信大家對“Python面向切面編程AOP及裝飾器怎么使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。