快速瞭解Python中的裝飾器,Python裝飾器

來源:互聯網
上載者:User

快速瞭解Python中的裝飾器,Python裝飾器

需要理解的一些概念

要理解Python中的裝飾器,我覺得還是應該從最基本的概念開始:

裝飾器模式:所謂的裝飾器模式,可以簡單地理解為“在不改變原有內部實現的情況下,為函數或者類添加某種特性”。這樣我們就可以將一些與業務無關、具有通用性的代碼抽象出來,作為裝飾器附加到需要這些代碼的函數或者類之上。用面向切面編程的思想解釋就是“裝飾器應該是一個切面”。

函數是一等公民:意思就是函數可以被當成普通變數一樣使用。在Python中,可以把函數賦值給變數,可以將函數作為其它函數的參數,也可以將函數作為其它函數的傳回值。

閉包:我們都知道局部範圍可以引用全域範圍中的變數,相似的,當一個函數內部又定義了其它函數的時候,內建函式可以使用外部函數所在範圍的變數,這就是閉包。

從最簡單的裝飾器做起

理解完以上的概念之後,我們嘗試一下利用這些特性實現一個簡單的裝飾器。

首先明確一下需求,我們有時候會需要在函數調用時列印一個相應的日誌,雖然可以通過在所有需要列印日誌的函數代碼中嵌入列印日誌的代碼來實現,但這種方法不僅增加了許多重複代碼,而且在業務代碼中嵌入與業務無關的代碼增加了整體的耦合度。因此,我們需要實現一個裝飾器,這個裝飾器在函數調用時可以列印一個日誌記錄函數調用行為。

如果我們有以下函數foo,代表具體的業務函數:

def foo():  print('in function foo')

我們設想通過調用foo = deco(foo)實現給函數foo增加列印日誌的功能,並且不影響它原有的業務。那麼在這種設想下,裝飾器deco應該也是一個函數,它接收另一個函數作為輸入,並返回一個新的、經過裝飾的函數。在Python中,我們可以這麼寫:

def deco(func): # 接收一個函數作為參數  def new_func():    print(f'[log] run function {func.__name__}') # 此處使用了Python3.6的格式化字串    func() # 閉包,在內建函式中使用了外部函數的變數  return new_func # 將新函數作為傳回值返回

執行一下試試效果:

>>> foo = deco(foo)>>> foo()[log] run function fooin function foo

不錯,至此我們已經實現了一個最簡單的裝飾器!在上面的代碼中,裝飾器deco接收任意的函數作為參數,再在內部構造另一個函數,利用閉包的特性,可以在新函數裡調用存在於裝飾器deco局部範圍中的函數func。

神奇的@

按照上面那麼寫,每次我們都得為需要裝飾的函數賦一個新值,萬一函數或者裝飾器的數量增加了,手動寫賦值和函數調用就會變得非常麻煩。那麼在Python中,有沒有更優雅的寫法呢?答案是有的,你只需要一個@符號。

在Python中,當我們需要一個裝飾器時:

def deco(func):  def new_func():    print(f'[log] run function {func.__name__}')    func()  return new_func@decodef foo():  print('in function foo')

這個地方我們省略了函數的賦值,直接在函數foo定義的上一行加上@deco進行裝飾。運行一下試試看:

>>> foo()[log] run function fooin function foo

是不是感覺很神奇?其實這裡面沒什麼魔法,只不過是Python在處理函數定義的代碼時,幫你在其中把foo=deco(foo)的邏輯加上了而已。

裝飾器也想要參數

上面的代碼實現了在業務函數調用之前列印日誌的功能,那如果我們需要在業務代碼執行完之後列印一條自訂的訊息,怎麼辦呢?我們必須要讓我們的裝飾器可以接收自訂參數。

上面提到過,Python做的只是當寫了@deco時,把調用deco(func)的結果賦值給它裝飾的函數而已。順著這個邏輯,當我們需要一個帶參數的裝飾器時,代碼上應該是寫為@deco('some message'),這時Python將調用deco(msg)(func)的結果賦值給foo。那麼事情就變得簡單了,我們只需要在上面代碼的基礎上嵌套一層函數:

def deco(msg):  def inner_deco(func):    def new_func():      print(f'[log] run function {func.__name__}')      func()      print(f'[log] {msg}')    return new_func  return inner_deco@deco('some message')def foo():  print('in function foo')

執行一下試試:

>>> foo()[log] run function fooin function foo[log] some message

不支援帶參數的被裝飾函數的裝飾器不是好裝飾器

上面的代碼還是有問題,因為我們只考慮了函數foo沒有參數時的情況,萬一函數foo帶了參數,這個裝飾器就會丟失參數資訊,這不是一個合格的裝飾器應該出現的情況。所以,我們藉助Python中的*args和**kwargs使被裝飾的函數可以支援傳入任意參數:

def deco(msg):  def inner_deco(func):    def new_func(*args, **kwargs):      print(f'[log] run function {func.__name__}')      func(*args, **kwargs)      print(f'[log] {msg}')    return new_func  return inner_deco@deco('some message')def foo(a, b=None):  print('in function foo')  print(f'a is {a} & b is {b}')

這樣一來,無論函數foo的參數列表是怎麼樣的都不會有問題了:

>>> foo('hello')[log] run function fooin function fooa is hello & b is None[log] some message

不支援有傳回值的被裝飾函數的裝飾器不是好裝飾器

別忘了,到目前為止,我們寫的函數foo都沒有傳回值,如果函數foo有傳回值怎麼辦呢?我想你心裡應該有答案了:

def deco(msg):  def inner_deco(func):    def new_func(*args, **kwargs):      print(f'[log] run function {func.__name__}')      rlt = func(*args, **kwargs)      print(f'[log] {msg}')      return rlt    return new_func  return inner_deco@deco('some message')def foo(a, b=None):  print('in function foo')  print(f'a is {a} & b is {b}')  return 'ok'

由於裝飾器在原函數執行完之後還有別的操作,所以應該把傳回值暫存起來,等到裝飾器的邏輯執行完畢,才返回最終結果。這就是我們的最終版裝飾器了!

>>> rlt = foo('a')[log] run function fooin function fooa is a & b is None[log] some message>>> print(rlt)ok

有沒有更騷的操作?

當然有啊!我標題都這麼寫了難不成會沒有?

在Python中,你可以使用類來作為裝飾器:

class Deco(object):  def __call__(self, func):    def new_func():      print(f'[log] run function {func.__name__}')      func()    return new_func@Deco()def foo():  print('in function foo')
>>> foo()[log] run function fooin function foo

這麼做的好處就是可以利用類更好地管理參數和調用邏輯,比起之前三層函數嵌套的形式是不是清晰多了?

在Python中,你還可以使用裝飾器來裝飾一個類,比如這樣:

def add_doc(doc):  def deco(cls):    cls.__doc__ = doc    return cls  return deco@add_doc('this is the doc of Cls')class Cls(object):  pass

來看看效果:

>>> help(Cls)Help on class Cls in module __main__:class Cls(builtins.object) | this is the doc of Cls

上面的代碼只是一個樣本,展示裝飾器怎麼裝飾一個類而已,不是說在實際情況下應該這麼用。大部分的情況下,我們對於類的拓展應該是通過繼承而不是裝飾。

具體怎麼巧妙地利用裝飾器就要靠大家發揮自己的想象力了。

總結

以上就是本文關於快速瞭解Python中的裝飾器的全部內容,希望對大家有所協助。感興趣的朋友可以繼續參閱本站其他相關專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支援!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.