目錄
設定問題
裝飾器
參數和關鍵字參數
高階裝飾器
首頁 後端開發 Python教學 一文讀懂 Python 裝飾器

一文讀懂 Python 裝飾器

Apr 12, 2023 pm 09:40 PM
python 裝飾器

一文讀懂 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中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

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

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

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

熱門話題

Java教學
1664
14
CakePHP 教程
1423
52
Laravel 教程
1317
25
PHP教程
1268
29
C# 教程
1248
24
PHP和Python:解釋了不同的範例 PHP和Python:解釋了不同的範例 Apr 18, 2025 am 12:26 AM

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

在PHP和Python之間進行選擇:指南 在PHP和Python之間進行選擇:指南 Apr 18, 2025 am 12:24 AM

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

PHP和Python:深入了解他們的歷史 PHP和Python:深入了解他們的歷史 Apr 18, 2025 am 12:25 AM

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

sublime怎麼運行代碼python sublime怎麼運行代碼python Apr 16, 2025 am 08:48 AM

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

Python vs. JavaScript:學習曲線和易用性 Python vs. JavaScript:學習曲線和易用性 Apr 16, 2025 am 12:12 AM

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

Golang vs. Python:性能和可伸縮性 Golang vs. Python:性能和可伸縮性 Apr 19, 2025 am 12:18 AM

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

vscode在哪寫代碼 vscode在哪寫代碼 Apr 15, 2025 pm 09:54 PM

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

notepad 怎麼運行python notepad 怎麼運行python Apr 16, 2025 pm 07:33 PM

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

See all articles