第19天:裝飾器(Decorators)
課程簡介
裝飾器是 Python 中的一個強大功能,能夠在不修改原有程式碼的情況下,對函數或方法進行擴展和修改。透過裝飾器,我們可以將重複的邏輯從多個函數中抽取出來,並保持程式的簡潔和高可讀性。今天我們將學習如何定義和使用裝飾器,並探討一些常見的應用場景。
學習內容
1. 裝飾器的基本概念
裝飾器本質上是一個高階函數,接收另一個函數作為參數,並返回一個新的函數。它通常使用 @decorator_name
語法來套用到一個函數上。
範例:
1
2
3
4
5
6
7
8
9
10
11
12
def decorator(func):
def wrapper():
print("這是在函數執行之前")
func()
print("這是在函數執行之後")
return wrapper
@decorator
def say_hello():
print("Hello, World!")
say_hello()
輸出結果:
1
2
3
這是在函數執行之前
Hello, World!
這是在函數執行之後
在這個範例中,decorator
函數接受 say_hello
作為參數,並返回一個新的 wrapper
函數,該函數在原來的 say_hello
函數前後添加了額外的行為。
2. 使用帶參數的裝飾器
有時候,我們的函數需要接受參數。為了處理這種情況,我們需要在裝飾器的 wrapper
函數中添加對參數的支持。
範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
def decorator(func):
def wrapper(*args, **kwargs):
print("這是在函數執行之前")
result = func(*args, **kwargs)
print("這是在函數執行之後")
return result
return wrapper
@decorator
def add(a, b):
return a + b
print(add(2, 3))
輸出結果:
1
2
3
這是在函數執行之前
這是在函數執行之後
5
這裡的 wrapper
函數接受了 *args
和 **kwargs
,以便能夠處理任何數量的參數。
3. 裝飾器的應用場景
- 日誌記錄:在函數執行之前或之後記錄日誌。
- 檢查權限:在執行某些操作之前,檢查使用者是否有權限。
- 計算執行時間:測量函數的執行時間,方便進行效能分析。
範例(計算函數執行時間):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"函數執行時間: {end_time - start_time} 秒")
return result
return wrapper
@timer_decorator
def slow_function():
time.sleep(2)
print("函數執行完成")
slow_function()
輸出結果:
1
2
函數執行完成
函數執行時間: 2.000123 秒
4. 裝飾器的嵌套
我們可以同時對一個函數應用多個裝飾器,這被稱為裝飾器的嵌套。多個裝飾器會按照從內到外的順序執行。
範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def decorator_one(func):
def wrapper(*args, **kwargs):
print("裝飾器一:執行之前")
result = func(*args, **kwargs)
print("裝飾器一:執行之後")
return result
return wrapper
def decorator_two(func):
def wrapper(*args, **kwargs):
print("裝飾器二:執行之前")
result = func(*args, **kwargs)
print("裝飾器二:執行之後")
return result
return wrapper
@decorator_one
@decorator_two
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
輸出結果:
1
2
3
4
5
裝飾器一:執行之前
裝飾器二:執行之前
Hello, Alice!
裝飾器二:執行之後
裝飾器一:執行之後
5. 使用 functools.wraps
保留原函數資訊
使用裝飾器時,原函數的名稱和文件字串(docstring)會被覆蓋,這可能會導致一些問題。為了保留原函數的資訊,我們可以使用 functools.wraps
。
範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import functools
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("執行裝飾器")
return func(*args, **kwargs)
return wrapper
@decorator
def example():
"""這是一個範例函數。"""
print("執行範例函數")
print(example.__name__) # 輸出: example
print(example.__doc__) # 輸出: 這是一個範例函數。
functools.wraps
確保了裝飾後的函數保留原來的名稱和說明。
教學重點
- 裝飾器的基本概念:理解如何使用裝飾器來修改函數的行為,而不改變原始程式碼。
- 帶參數的裝飾器:學會如何處理帶有參數的函數。
- 常見應用場景:學習裝飾器在日誌記錄、權限檢查和效能分析中的應用。
- 裝飾器嵌套:理解如何應用多個裝飾器到同一個函數。
functools.wraps
:掌握如何保留裝飾後函數的原始資訊。
任務
- 寫一個裝飾器
log_decorator
,在函數執行之前和之後記錄 “函數執行前” 和 “函數執行後”。 - 寫一個裝飾器
authenticate
,檢查使用者是否有權限執行某個函數,若無權限則拋出錯誤訊息。 - 創建一個
performance_timer
裝飾器,計算函數的執行時間,並顯示結果。 - 使用多個裝飾器同時裝飾一個函數,並觀察裝飾器的執行順序。
- 使用
functools.wraps
來保留被裝飾函數的名稱和文件字串。
本文章以 CC BY 4.0 授權