文章

第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:掌握如何保留裝飾後函數的原始資訊。

任務

  1. 寫一個裝飾器 log_decorator,在函數執行之前和之後記錄 “函數執行前” 和 “函數執行後”。
  2. 寫一個裝飾器 authenticate,檢查使用者是否有權限執行某個函數,若無權限則拋出錯誤訊息。
  3. 創建一個 performance_timer 裝飾器,計算函數的執行時間,並顯示結果。
  4. 使用多個裝飾器同時裝飾一個函數,並觀察裝飾器的執行順序。
  5. 使用 functools.wraps 來保留被裝飾函數的名稱和文件字串。
本文章以 CC BY 4.0 授權