<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
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
在函數裝飾器方面,很多人搞不清楚,是因為裝飾器可以用函數實現(像上面),也可以用類實現。因為函數和類都是物件,同樣可以作為被裝飾的物件,所以根據被裝飾的物件不同,一同有下面四種情況:
下面我們依次來說明一下這四種情況的使用。
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 'function'>
此為最常見的裝飾器,用於裝飾函數,返回的是一個函數。
也就是通過類來實現裝飾器的功能而已。通過類的 __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__) # 輸出結果: <class 'function'>
以上所示,只是將用函數定義的裝飾器改為使用類來實現而已。還是用於裝飾函數,因為在類的 __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__) # 輸出結果: <class 'function'>
如上是比較方便簡答的,使用類定義函數裝飾器,且返回物件為函數,後設資料保留。
下面重點來啦,我們常見的裝飾器都是用於裝飾函數的,返回的物件也是一個函數,而要裝飾類,那麼返回的物件就要是類,且類的後設資料等也要保留。
不怕丟臉的說,目前我還不知道怎麼實現完美的類裝飾器,在裝飾類的時候,一般有兩種方法:
返回一個函數,實現類在建立範例的前後執行操作,並正常返回此類的範例。但是這樣經過裝飾器的類就屬於函數了,其無法繼承,但可以正常呼叫建立範例。
如下:
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 在類初始化後,做一些操作 <class '__main__.A'> <class 'function'>
如上所示,就是第一種方法。
接上文,返回一個類,實現類在建立範例的前後執行操作,但類已經改變了,建立的範例也已經不是原本類的範例了。
看下面範例:
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
在類初始化後,做一些操作
<class '__main__.desc.<locals>.decorator.<locals>.Wrapper'>
<class 'type'>
Wrapper
如上,看到了嗎,通過在函數中新定義類,並返回類,這樣函數還是類,但是經過裝飾器後,類 A
已經變成了類 Wrapper
,且生成的範例 a
也是類 Wrapper
的範例,即使通過 __getattr__
和 __setattr__
兩個方法,使得範例a的屬性都是在由類 A
建立的範例 wrapped
的屬性,但是類的後設資料無法改變。很多內建的方法也就會有問題。我個人是不推薦這種做法的!
所以,我推薦在程式碼中,儘量避免類裝飾器的使用,如果要在類中做一些操作,完全可以通過修改類的魔法方法,繼承,元類等等方式來實現。如果避免不了,那也請謹慎處理。
到此這篇關於Python 面向切面程式設計 AOP 及裝飾器的文章就介紹到這了,更多相關Python AOP 內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45