溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python中裝飾器的示例分析

發(fā)布時間:2021-07-24 09:48:24 來源:億速云 閱讀:94 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要為大家展示了“Python中裝飾器的示例分析”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Python中裝飾器的示例分析”這篇文章吧。

需要理解的一些概念

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

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

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

閉包:我們都知道局部作用域可以引用全局作用域中的變量,相似的,當(dāng)一個函數(shù)內(nèi)部又定義了其它函數(shù)的時候,內(nèi)部函數(shù)可以使用外部函數(shù)所在作用域的變量,這就是閉包。

從最簡單的裝飾器做起

理解完以上的概念之后,我們嘗試一下利用這些特性實(shí)現(xiàn)一個簡單的裝飾器。

首先明確一下需求,我們有時候會需要在函數(shù)調(diào)用時打印一個相應(yīng)的日志,雖然可以通過在所有需要打印日志的函數(shù)代碼中嵌入打印日志的代碼來實(shí)現(xiàn),但這種方法不僅增加了許多重復(fù)代碼,而且在業(yè)務(wù)代碼中嵌入與業(yè)務(wù)無關(guān)的代碼增加了整體的耦合度。因此,我們需要實(shí)現(xiàn)一個裝飾器,這個裝飾器在函數(shù)調(diào)用時可以打印一個日志記錄函數(shù)調(diào)用行為。

如果我們有以下函數(shù)foo,代表具體的業(yè)務(wù)函數(shù):

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

我們設(shè)想通過調(diào)用foo = deco(foo)實(shí)現(xiàn)給函數(shù)foo增加打印日志的功能,并且不影響它原有的業(yè)務(wù)。那么在這種設(shè)想下,裝飾器deco應(yīng)該也是一個函數(shù),它接收另一個函數(shù)作為輸入,并返回一個新的、經(jīng)過裝飾的函數(shù)。在Python中,我們可以這么寫:

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

執(zhí)行一下試試效果:

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

不錯,至此我們已經(jīng)實(shí)現(xiàn)了一個最簡單的裝飾器!在上面的代碼中,裝飾器deco接收任意的函數(shù)作為參數(shù),再在內(nèi)部構(gòu)造另一個函數(shù),利用閉包的特性,可以在新函數(shù)里調(diào)用存在于裝飾器deco局部作用域中的函數(shù)func。

神奇的@

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

在Python中,當(dāng)我們需要一個裝飾器時:

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

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

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

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

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

裝飾器也想要參數(shù)

上面的代碼實(shí)現(xiàn)了在業(yè)務(wù)函數(shù)調(diào)用之前打印日志的功能,那如果我們需要在業(yè)務(wù)代碼執(zhí)行完之后打印一條自定義的消息,怎么辦呢?我們必須要讓我們的裝飾器可以接收自定義參數(shù)。

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

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')

執(zhí)行一下試試:

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

不支持帶參數(shù)的被裝飾函數(shù)的裝飾器不是好裝飾器

上面的代碼還是有問題,因?yàn)槲覀冎豢紤]了函數(shù)foo沒有參數(shù)時的情況,萬一函數(shù)foo帶了參數(shù),這個裝飾器就會丟失參數(shù)信息,這不是一個合格的裝飾器應(yīng)該出現(xiàn)的情況。所以,我們借助Python中的*args和**kwargs使被裝飾的函數(shù)可以支持傳入任意參數(shù):

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 ')

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

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

不支持有返回值的被裝飾函數(shù)的裝飾器不是好裝飾器

別忘了,到目前為止,我們寫的函數(shù)foo都沒有返回值,如果函數(shù)foo有返回值怎么辦呢?我想你心里應(yīng)該有答案了:

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 ')
  return 'ok'

由于裝飾器在原函數(shù)執(zhí)行完之后還有別的操作,所以應(yīng)該把返回值暫存起來,等到裝飾器的邏輯執(zhí)行完畢,才返回最終結(jié)果。這就是我們的最終版裝飾器了!

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

有沒有更騷的操作?

當(dāng)然有??!我標(biāo)題都這么寫了難不成會沒有?

在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 foo
in function foo

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

在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

上面的代碼只是一個示例,展示裝飾器怎么裝飾一個類而已,不是說在實(shí)際情況下應(yīng)該這么用。大部分的情況下,我們對于類的拓展應(yīng)該是通過繼承而不是裝飾。

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

以上是“Python中裝飾器的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI