本篇是 Python 系列教程第 13 篇,更多內容敬請訪問我的 Python 合集
Python 裝飾器是一種強大的工具,用於修改或增強函式或方法的行為,而無需更改其原始碼。裝飾器本質上是一個接收函式作為引數的函式,並返回一個新的函式。裝飾器的用途包括日誌記錄、效能測試、事務處理、快取、許可權校驗等
1 基本語法
裝飾器的基本語法是在函式定義之前使用@
符號,緊跟著裝飾器的名字。例如:
# 定義一個裝飾器,引數為被裝飾的方法
def my_decorator(func):
def wrapper():
print("方法執行前")
func()
print("方法執行後")
return wrapper
# 用“@”使用裝飾器
@my_decorator
def say_hello():
print("Hello!")
say_hello()
這段程式碼會輸出:
方法執行前
Hello!
方法執行後
2 引數傳遞
如果被裝飾的函式需要引數,裝飾器也需要相應地處理這些引數:
def my_decorator(func):
def wrapper(name):
print("方法執行前")
func(name)
print("方法執行後")
return wrapper
@my_decorator
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
輸出:
方法執行前
Hello, Alice!
方法執行後
引數可以用可變引數,比較靈活,如下:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("方法執行前")
func(*args, **kwargs)
print("方法執行後")
return wrapper
@my_decorator
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
3 使用多個裝飾器
你可以為同一個函式使用多個裝飾器:
def decorator_A(func):
print("enter A")
def wrapper():
print("Before A")
func()
print("After A")
print("out A")
return wrapper
def decorator_B(func):
print("enter B")
def wrapper():
print("Before B")
func()
print("After B")
print("out B")
return wrapper
@decorator_A
@decorator_B
def my_function():
print("Inside my_function")
# 執行被裝飾器裝飾的函式
my_function()
輸出:
enter B
out B
enter A
out A
Before A
Before B
Inside my_function
After B
After A
注意列印結果的順序。
為了方便表達我們先把靠近被修飾方法的裝飾器叫內層裝飾器,如示例中的@decorator_B
,不靠近的叫外層裝飾器,如示例中的@decorator_A
。
在閉包wrapper
外面的程式碼是內層裝飾器先執行,在閉包wrapper
內部的程式碼執行順序複雜一些:①外層裝飾器先執行func()
前面的程式碼->②內層裝飾器執行func()
前面的程式碼->③執行func()
->④內層裝飾器執行func()
後面的程式碼->⑤外層裝飾器執行func()
後面的程式碼。
4 給裝飾器傳參
裝飾器本身可以接受引數,可以根據傳入的不同引數來改變裝飾器的行為。
前面的例子都是沒有引數的裝飾器,如果我們想要給裝飾器傳參該怎麼辦呢?於是我們就思考一下,什麼東東可以接收引數呢,答案是函式。bingo!Python也是這樣設計的,我們只需要在裝飾器外面包裹一層函式,就可以把引數傳遞給函式進而傳遞給裝飾器了。
可以這樣定義裝飾器:
def repeat(num_times):
def decorator_repeat(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
輸出:
Hello, Alice!
Hello, Alice!
Hello, Alice!
這樣就定義了一個根據傳入裝飾器的數值執行指定次數函式的裝飾器。
5 類作為裝飾器
5.1 __call__
方法
裝飾器不僅僅可以是方法,也可以是類。這就不得不介紹一個特殊的方法__call__
。
Python的類只要實現了__call__
這個特殊方法,類的例項物件就可以像函式一樣被呼叫,因為當嘗試把物件寫成方法呼叫的寫法時(名稱+()),Python 直譯器會查詢該物件的 __call__
方法並呼叫它。
下面來看一個簡單的例子,演示__call__
的使用:
class Counter:
def __init__(self):
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"方法被呼叫了 {self.count} 次")
counter = Counter()
# 模擬呼叫
counter()
counter()
counter()
列印:
方法被呼叫了 1 次
方法被呼叫了 2 次
方法被呼叫了 3 次
5.2 類作為裝飾器
類作為裝飾器的一個主要優勢是可以方便地維護狀態,因為類可以有例項變數。
理解了__call__
之後,我們可以想到類作為裝飾器的原理是在類裡實現了__call__
方法,使得裝飾器的程式碼可以被執行。
下面我們定義一個記錄函式呼叫次數的裝飾器:
class CallCounter:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} 被呼叫了 {self.count} 次")
return self.func(*args, **kwargs)
@CallCounter
def say_hello(name):
print(f"Hello, {name}!")
# 呼叫被裝飾的函式
say_hello("Alice")
say_hello("Bob")
say_hello("Charlie")
# 輸出
# say_hello 被呼叫了 1 次
# Hello, Alice!
# say_hello 被呼叫了 2 次
# Hello, Bob!
# say_hello 被呼叫了 3 次
# Hello, Charlie!
程式碼解釋:
CallCounter
類有一個建構函式__init__
,它必須接受一個函式作為引數。- 類實現了
__call__
方法,這使得其例項可以像函式一樣被呼叫。 - 在
__call__
方法中,每次呼叫被裝飾的函式時,都會增加計數器count
的值,並列印出函式被呼叫的次數。 - 最後,
__call__
方法呼叫了原始函式self.func
並返回結果。