您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“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 foo in function foo
不錯,至此我們已經實現了一個最簡單的裝飾器!在上面的代碼中,裝飾器deco接收任意的函數作為參數,再在內部構造另一個函數,利用閉包的特性,可以在新函數里調用存在于裝飾器deco局部作用域中的函數func。
神奇的@
按照上面那么寫,每次我們都得為需要裝飾的函數賦一個新值,萬一函數或者裝飾器的數量增加了,手動寫賦值和函數調用就會變得非常麻煩。那么在Python中,有沒有更優雅的寫法呢?答案是有的,你只需要一個@符號。
在Python中,當我們需要一個裝飾器時:
def deco(func): def new_func(): print(f'[log] run function {func.__name__}') func() return new_func @deco def foo(): print('in function foo')
這個地方我們省略了函數的賦值,直接在函數foo定義的上一行加上@deco進行裝飾。運行一下試試看:
>>> foo() [log] run function foo in 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 foo in 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 foo in function foo a 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 foo in function foo a 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 foo in 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中裝飾器的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。