本文始發於我的公衆號:TechFlow,原創不易,求個關注程序員
今天是Python專題的第14篇文章,咱們繼續裝飾器的話題,來看看怎麼給裝飾器包裝方法,實現更多靈活的操做。web
在以前的文章當中,咱們實現了對裝飾器賦予參數,從而能夠經過傳入不一樣的參數來控制裝飾器中的邏輯。這樣作能夠大大地增長裝飾器的靈活性,可是仍然不足以解決全部的問題。編程
若是咱們面臨一個變更很頻繁的業務,之後也許須要加上一些當前想不到的邏輯,這個時候就沒有辦法僅僅經過參數來控制了。那麼有沒有辦法不只僅是傳入參數,而是能夠給裝飾器添加不一樣的邏輯呢?app
固然是有的,可是這個操做比較複雜,讓咱們抽絲剝繭,一點一點來吃透它。框架
首先咱們來看下setattr和getattr這兩個方法,attr是attribute的縮寫,也就是屬性的意思。咱們搞明白了這個單詞的意思以後就簡單了,根據字面能夠理解到,這兩個方法一個是設置屬性一個是獲取屬性。編輯器
是的,就是這麼簡單,沒錯。函數式編程
其中getattr尤爲簡單,基本上等價於使用.去獲取屬性。函數
咱們來看一個最簡單的例子,咱們先建立一個類,而後給它附上一個屬性。工具
class A:
def __init__(self): self.name = 'hello' 複製代碼
以後,咱們可使用getattr方法去得到它的name屬性:學習
a = A()
getattr(a, 'name') 複製代碼
有get天然就有set,咱們也能夠經過setattr爲它附上新的屬性。第二個參數是新增的屬性名稱,第三個參數是屬性的值。
setattr(a, 'age', 18)
複製代碼
這樣,當咱們去執行a.age的時候,就會得到18。這裏要注意的是,咱們只是單純地爲a這個實例建立了新的屬性,並無更改A這個類中的定義。因此其餘A這個類的實例並不會受到影響,另外若是咱們將多個值賦值給了同一個屬性名會發生覆蓋,也就是後面的覆蓋前面的。
屬性這個詞在Python中的定義是比較寬泛的,除了變量能夠稱做是屬性,函數也同樣能夠做爲屬性。也就是說咱們除了能夠添加一個變量以外,也能夠添加一個函數。
咱們來看個例子:
def print_log():
print('This is a log') 複製代碼
這是一個簡單的demo方法,咱們經過setattr將它賦值給實例a,那麼咱們就能夠在實例a中調用它了。
不只僅如此,類也同樣能夠經過setattr方法設置。
理解了setattr和getattr的用法以後,咱們不由有一個問題,咱們經過.操做不香嗎,爲何還要搞一個setattr和getattr出來呢?
若是咱們本身寫代碼寫着玩,固然是用.操做更方便,但若是是實際的開發場景。頗有可能咱們須要添加的屬性的名稱是個變量,而不是寫死的,也就是說是可配置的。這個時候就不能經過.了,咱們考慮問題的時候不能僅僅從功能入手,也須要思考一下它的使用場景。
setattr咱們都已經熟悉了,接下來回到正題。Python當中一切都是對象,一樣函數也是對象。既然函數也是對象,那麼咱們就能夠給函數也設置屬性。裝飾器的本質就是函數,因此咱們能夠給裝飾器內包裝的函數也設置屬性,爲了方便你們理解,我先不用setattr,讓你們看看單純的帶屬性的裝飾器是什麼樣的。
def decorate(func):
logmsg = func.__name__ @wraps(func) def wrapper(*args, **kwargs): print(logmsg) return func(*args, **kwargs) def set_message(newmsg): nonlocal logmsg logmsg = newmsg wrapper.set_message = set_message return wrapper 複製代碼
若是咱們把set_message這個方法拿掉的話,它就是一個普普統統的裝飾器。set_message方法當中,咱們使用nonlocal關鍵字修改了logmsg這個變量的值,而這個值會在裝飾器的包裝函數當中用到。也就是說咱們經過調用set_message方法,能夠修改這個裝飾器的運行結果和邏輯。
這裏,咱們沒用裝飾器,而是簡單地使用了.關鍵字來對它進行了賦值。仍是和以前說的同樣,這樣固然是能夠的,可是若是咱們想要配置這個name就作不到了。最多見的場景就是區分線上和測試環境,一種作法是在接口的名字以前加上一個標識,好比線上是online,測試環境是test或者是dev。經過這種方法區分不一樣環境的邏輯。
因此比較好的方法是將這個邏輯也寫成一個裝飾器,將被包裝的方法做爲參數傳入。若是你看明白了上一篇文章,熟悉裝飾器傳參的話,這段代碼對你來講應該很簡單。
def attach(obj):
@wraps(obj) def wrapper(func): setattr(obj, func.__name__, func) return func return wrapper 複製代碼
有了attach這個裝飾器以後,咱們只須要給set_message這個方法加上註解,將被包裝的函數做爲參數傳入便可。
@attach(wrapper)
def set_message(newmsg): nonlocal logmsg logmsg = newmsg 複製代碼
若是隻是想要實現功能,而不追求規範的話,可使用partial來簡化代碼,減小它的層次結構:
def attach(obj, func=None):
if func is None: return partial(attach_wrapper, obj) setattr(obj, func.__name__, func) return func 複製代碼
這樣寫也是能夠work的,只要熟悉partial的用法,應該也不難理解。
若是你是一個程序員,你面臨一個變更很頻繁的業務,你沒法預知以後的需求狀況,想要代碼有足夠大的機動餘地,這個時候能夠利用強大的setattr給程序留一個「後門」,方便後面臨時修改。
具體的作法其實很簡單,咱們在裝飾器當中定義一個dict,用來存儲自定義的函數。再實現一個set_func方法將自定義的函數存儲進這個dict當中,只有就能夠經過參數,在不修改裝飾器的狀況下自由變動裝飾器內的邏輯了。
咱們來看代碼:
def decorate(func):
func_dict = {} @wraps(func) def wrapper(*args, **kwargs): # 經過key來選擇應該調用哪個函數做爲裝飾器的邏輯 if kwargs.get('key') is not None: func_dict[kwargs['key']](*args, **kwargs) return func(*args, **kwargs) # 將函數名和函數做爲參數傳入,存儲在dict中 @attach(wrapper) def set_func(func_name, func): nonlocal func_dict func_dict[func_name] = func return wrapper 複製代碼
咱們再來看一個使用的例子:
def test(*args, **kw):
print('test') add.set_func('test', test) add(3, 4, key='test') 複製代碼
這樣,咱們就把test方法中的邏輯放入了裝飾器當中,只有咱們須要,咱們還能夠寫出其餘的方法,來自定義咱們對裝飾器的需求,而又不須要修改裝飾器內部的邏輯。不只如此,咱們還能夠在主體函數的先後都加上這樣的邏輯,真的能夠說是隨心所欲了。
固然通常狀況下咱們用不到這樣的騷操做,可是可以寫出來或者說看懂這樣的功能,那就說明關於裝飾器的理解已經算是入門了。
裝飾器能夠說是函數式編程在Python當中最重要的使用渠道,在許多Python工具和框架當中大量使用。其實咱們學習的並不只僅是裝飾器的一兩種奇淫技巧,也是函數式編程的一些思想和理念。當咱們將這些理念理解深入了以後,不只僅是Python,一樣能夠在許多其餘的領域得到日新月異的進步。
各位看官大人,賞個關注吧~