一文讀懂 Python 裝飾器
Python 是一種對新手很友善的語言。但是,它也有很多較難掌握的高級功能,例如裝飾器(decorator)。許多初學者一直不理解裝飾器及其工作原理,在這篇文章中,我們將介紹裝飾器的來龍去脈。
在 Python 中,函數是一種非常靈活的結構,我們可以把它賦值給變數、當作參數傳遞給另一個函數,或是當成某個函數的輸出。裝飾器本質上也是一種函數,它可以讓它函數在不經過修改的情況下增加一些功能。
這也就是「裝飾」的意義,這種「裝飾」本身代表著一種功能,如果用它來修飾不同的函數,那麼也就是為這些函數增加這種功能。
一般而言,我們可以使用裝飾器提供的 @ 語法糖(Syntactic Sugar)來修飾其它函數或物件。如下所示我們用@dec 裝飾器修飾函數func ():
@dec def func(): pass
理解裝飾器的最好方式是了解裝飾器解決什麼問題,本文將從具體問題出發一步步引出裝飾器,並展示它的優雅與強大。
設定問題
為了解裝飾器的目的,接下來我們來看一個簡單的範例。假如你有一個簡單的加法函數dec.py,第二個參數的預設值為10:
# dec.py def add(x, y=10): return x + y
我們來更認真地看一下這個加法函數:
>>> add(10, 20) 30 >>> add <function add at 0x7fce0da2fe18> >>> add.__name__ 'add' >>> add.__module__ '__main__' >>> add.__defaults__ # default value of the `add` function (10,) >>> add.__code__.co_varnames # the variable names of the `add` function ('x', 'y')
我們不需要理解這些都是什麼,只需要記住Python 中的每個函數都是對象,它們有各種屬性和方法。你也可以透過inspect 模組查看add() 函數的原始碼:
>>> from inspect import getsource >>> print(getsource(add)) def add(x, y=10): return x + y
現在你以某種方式使用該加法函數,例如你使用一些操作來測試該函數:
# dec.py from time import time def add(x, y=10): return x + y print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) Output: i add(10) 20 add(20, 30) 50 add("a", "b") ab
假如你想了解每個操作的時間,可以呼叫time 模組:
# dec.py from time import time def add(x, y=10): return x + y before = time() print('add(10)', add(10)) after = time() print('time taken: ', after - before) before = time() print('add(20, 30)', add(20, 30)) after = time() print('time taken: ', after - before) before = time() print('add("a", "b")', add("a", "b")) after = time() print('time taken: ', after - before) Output: add(10) 20 time taken:6.699562072753906e-05 add(20, 30) 50 time taken:6.9141387939453125e-06 add("a", "b") ab time taken:6.9141387939453125e-06
現在,你作為一個程式設計人員是不是有些手癢,畢竟我們不喜歡總是複製貼上相同的程式碼。現在的程式碼可讀性不強,如果你想改變什麼,你就得修改所有出現的地方,Python 肯定有更好的方式。
我們可以按照以下做法,直接在 add 函數中捕捉運行時間:
# dec.py from time import time def add(x, y=10): before = time() rv = x + y after = time() print('time taken: ', after - before) return rv print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b"))
這種方法肯定比前一種要好。但是如果你還有另一個函數,那麼這似乎就不方便了。當我們有多個函數時:
# dec.py from time import time def add(x, y=10): before = time() rv = x + y after = time() print('time taken: ', after - before) return rv def sub(x, y=10): return x - y print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) print('sub(10)', sub(10)) print('sub(20, 30)', sub(20, 30))
因為 add 和 sub 都是函數,我們可以利用這一點寫一個 timer 函數。我們希望timer 能計算一個函數的運算時間:
def timer(func, x, y=10): before = time() rv = func(x, y) after = time() print('time taken: ', after - before) return rv
這很不錯,不過我們必須使用timer 函數包裝不同的函數,如下所示:
print('add(10)', timer(add,10)))
現在預設值還是10 嗎?未必。那麼如何做得更好呢?
這裡有一個主意:創建一個新的timer 函數,並包裝其他函數,然後返回包裝後的函數:
def timer(func): def f(x, y=10): before = time() rv = func(x, y) after = time() print('time taken: ', after - before) return rv return f
現在,你只需用timer 包裝一下add和sub 函數:
add = timer(add)
這樣就可以了!以下是完整程式碼:
# dec.py from time import time def timer(func): def f(x, y=10): before = time() rv = func(x, y) after = time() print('time taken: ', after - before) return rv return f def add(x, y=10): return x + y add = timer(add) def sub(x, y=10): return x - y sub = timer(sub) print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) print('sub(10)', sub(10)) print('sub(20, 30)', sub(20, 30)) Output: time taken:0.0 add(10) 20 time taken:9.5367431640625e-07 add(20, 30) 50 time taken:0.0 add("a", "b") ab time taken:9.5367431640625e-07 sub(10) 0 time taken:9.5367431640625e-07 sub(20, 30) -10
我們來總結一下這個過程:我們有一個函數(例如 add 函數),然後用一個動作(例如計時)包裝該函數。包裝的結果是一個新函數,能實現某些新功能。
當然了,預設值還有點問題,我們稍後會解決它。
裝飾器
現在,上面的解決方案以及非常接近裝飾器的思想了,使用常見行為包裝某個具體的函數,這種模式就是裝飾器在做的事。使用裝飾器後的程式碼是:
def add(x, y=10): return x + y add = timer(add) You write: @timer def add(x, y=10): return x + y
它們的作用是一樣的,這就是 Python 裝飾器的作用。它實現的作用類似 add = timer(add),只不過裝飾器把句法放在函數上面,句法更簡單:@timer。
# dec.py from time import time def timer(func): def f(x, y=10): before = time() rv = func(x, y) after = time() print('time taken: ', after - before) return rv return f @timer def add(x, y=10): return x + y @timer def sub(x, y=10): return x - y print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) print('sub(10)', sub(10)) print('sub(20, 30)', sub(20, 30))
參數和關鍵字參數
現在,還有一個小問題沒有解決。在 timer 函數中,我們將參數 x 和 y 寫死了,即指定 y 的預設值為 10。有一種方法可以傳輸該函數的參數和關鍵字參數,即 *args 和 **kwargs。參數是函數的標準參數(在本例中 x 為參數),關鍵字參數是已具備預設值的參數(本例中是 y=10)。程式碼如下:
# dec.py from time import time def timer(func): def f(*args, **kwargs): before = time() rv = func(*args, **kwargs) after = time() print('time taken: ', after - before) return rv return f @timer def add(x, y=10): return x + y @timer def sub(x, y=10): return x - y print('add(10)', add(10)) print('add(20, 30)', add(20, 30)) print('add("a", "b")', add("a", "b")) print('sub(10)', sub(10)) print('sub(20, 30)', sub(20, 30))
現在,該 timer 函數可以處理任意函數、任意參數和任意預設值設定了,因為它只是將這些參數傳輸到函數中。
高階裝飾器
你們可能會懷疑:如果我們可以用一個函數包裝另一個函數來加入有用的行為,那麼我們可以再進一步嗎?我們用一個函數包裝另一個函數,再被另一個函數包裝嗎?
可以!事實上,函數的深度可以隨你的意。例如,你要寫一個裝飾器來執行某個函數 n 次。如下所示:
def ntimes(n): def inner(f): def wrapper(*args, **kwargs): for _ in range(n): rv = f(*args, **kwargs) return rv return wrapper return inner
然後你可以使用上述函數包裝另一個函數,例如前文中的 add 函數:
@ntimes(3) def add(x, y): print(x + y) return x + y
輸出的語句表示程式碼確實執行了 3 次。
以上是一文讀懂 Python 裝飾器的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。

PHP適合網頁開發和快速原型開發,Python適用於數據科學和機器學習。 1.PHP用於動態網頁開發,語法簡單,適合快速開發。 2.Python語法簡潔,適用於多領域,庫生態系統強大。

PHP起源於1994年,由RasmusLerdorf開發,最初用於跟踪網站訪問者,逐漸演變為服務器端腳本語言,廣泛應用於網頁開發。 Python由GuidovanRossum於1980年代末開發,1991年首次發布,強調代碼可讀性和簡潔性,適用於科學計算、數據分析等領域。

在 Sublime Text 中運行 Python 代碼,需先安裝 Python 插件,再創建 .py 文件並編寫代碼,最後按 Ctrl B 運行代碼,輸出會在控制台中顯示。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Golang在性能和可擴展性方面優於Python。 1)Golang的編譯型特性和高效並發模型使其在高並發場景下表現出色。 2)Python作為解釋型語言,執行速度較慢,但通過工具如Cython可優化性能。

在 Visual Studio Code(VSCode)中編寫代碼簡單易行,只需安裝 VSCode、創建項目、選擇語言、創建文件、編寫代碼、保存並運行即可。 VSCode 的優點包括跨平台、免費開源、強大功能、擴展豐富,以及輕量快速。

在 Notepad 中運行 Python 代碼需要安裝 Python 可執行文件和 NppExec 插件。安裝 Python 並為其添加 PATH 後,在 NppExec 插件中配置命令為“python”、參數為“{CURRENT_DIRECTORY}{FILE_NAME}”,即可在 Notepad 中通過快捷鍵“F6”運行 Python 代碼。
