怎麼在Python裝飾器中自定義功能呢?用這種方法讓你「隨心所欲」

本文始發於我的公衆號:TechFlow,原創不易,求個關注程序員


今天是Python專題的第14篇文章,咱們繼續裝飾器的話題,來看看怎麼給裝飾器包裝方法,實現更多靈活的操做。web

在以前的文章當中,咱們實現了對裝飾器賦予參數,從而能夠經過傳入不一樣的參數來控制裝飾器中的邏輯。這樣作能夠大大地增長裝飾器的靈活性,可是仍然不足以解決全部的問題。編程

若是咱們面臨一個變更很頻繁的業務,之後也許須要加上一些當前想不到的邏輯,這個時候就沒有辦法僅僅經過參數來控制了。那麼有沒有辦法不只僅是傳入參數,而是能夠給裝飾器添加不一樣的邏輯呢?app

固然是有的,可是這個操做比較複雜,讓咱們抽絲剝繭,一點一點來吃透它。框架

setattr和getattr操做

首先咱們來看下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,一樣能夠在許多其餘的領域得到日新月異的進步。

各位看官大人,賞個關注吧~

相關文章
相關標籤/搜索