2026年10月12日 星期一

Python Decorator 裝飾器:5 個簡單又經典的範例

PYTHON 入門|函式的額外能力

Python Decorator 裝飾器:5 個簡單又經典的範例

裝飾器(Decorator)可以在不改動原函式主要程式碼的情況下,替它加上「執行前處理」或「執行後處理」。常見用途包括顯示訊息、記錄執行時間、檢查權限與保留函式資訊。

先記住一句話:裝飾器接收一個函式,回傳一個包裝函式;呼叫原函式時,就會先經過這層包裝。

一、裝飾器的運作流程

在函式定義上方寫 @my_decorator,Python 會把函式交給裝飾器處理。下面流程圖以「前後訊息」為例:

呼叫 say_hello()使用者呼叫函式 wrapper()裝飾器包裝層 func()原函式內容 回傳結果交回呼叫端 執行前處理執行後處理也可放在 func() 之後
呼叫流程:使用者呼叫包裝後的函式,wrapper 再呼叫原函式,最後把結果交回。

最常見的結構如下。*args 與 **kwargs 讓包裝函式可以接收不同數量的位置參數與關鍵字參數;return 則把原函式的結果傳回去。

def decorator(func):
    def wrapper(*args, **kwargs):
        # 額外處理:執行原函式之前
        result = func(*args, **kwargs)
        # 額外處理:執行原函式之後
        return result
    return wrapper

二、範例 1:在函式前後顯示訊息

def my_decorator(func):
    def wrapper():
        print("開始執行")
        func()
        print("執行結束")
    return wrapper


@my_decorator
def say_hello():
    print("Hello, Python!")


say_hello()

執行結果

開始執行
Hello, Python!
執行結束

程式解說

  1. my_decorator(func) 收到原本的 say_hello 函式。
  2. 裝飾器建立 wrapper,在呼叫原函式前印出「開始執行」,呼叫後再印出「執行結束」。
  3. return wrapper 把包裝函式交回去,因此之後呼叫 say_hello(),實際上執行的是 wrapper。
  4. @my_decorator 是簡寫,等同於 say_hello = my_decorator(say_hello)。

三、範例 2:讓裝飾器支援函式參數

def show_call(func):
    def wrapper(*args, **kwargs):
        print(f"正在呼叫:{func.__name__}")
        return func(*args, **kwargs)
    return wrapper


@show_call
def greet(name):
    print(f"你好,{name}!")


greet("小明")

執行結果

正在呼叫:greet
你好,小明!

程式解說

  • *args 收集位置參數,例如這裡的 "小明";**kwargs 收集像 name="小明" 這種關鍵字參數。
  • func.__name__ 取得原函式名稱,方便記錄目前呼叫哪個函式。
  • func(*args, **kwargs) 再把收到的參數完整傳給原函式。
  • return 會保留原函式的回傳值;若少了它,呼叫端通常會收到 None。

四、範例 3:計算函式執行時間

import time


def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()

        print(f"{func.__name__} 執行時間:{end - start:.4f} 秒")
        return result
    return wrapper


@timer
def count_numbers():
    total = sum(range(1_000_000))
    return total


print(count_numbers())

執行結果(時間會因電腦而不同)

count_numbers 執行時間:0.0284 秒
499999500000

程式解說

  • start 記錄函式執行前的時間;原函式完成後用 end 記錄時間。
  • end - start 是執行所花的秒數,:.4f 表示顯示到小數點後四位。
  • 原函式回傳總和,包裝函式將結果存在 result,印出時間後再回傳,因此外層的 print 仍能印出總和。
  • 範例中的時間只是示意;實際數值會受電腦與當時負載影響。

五、範例 4:執行函式前檢查登入狀態

def login_required(func):
    def wrapper(is_logged_in):
        if not is_logged_in:
            print("請先登入")
            return

        return func(is_logged_in)
    return wrapper


@login_required
def view_profile(is_logged_in):
    print("顯示個人資料")


view_profile(False)
view_profile(True)

執行結果

請先登入
顯示個人資料

程式解說

  • wrapper 先檢查 is_logged_in 的值。
  • 若值是 False,印出提示後用 return 結束,不再執行原函式。
  • 若值是 True,程式呼叫 func(is_logged_in),於是顯示個人資料。
  • 這是權限檢查的教學示範;實際網站應由登入系統與伺服器端驗證使用者身分。

六、範例 5:用 wraps 保留原函式資訊

from functools import wraps


def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("執行前")
        return func(*args, **kwargs)
    return wrapper


@my_decorator
def say_hello():
    """向使用者打招呼"""
    print("Hello!")


print(say_hello.__name__)
print(say_hello.__doc__)
say_hello()

執行結果

say_hello
向使用者打招呼
執行前
Hello!

程式解說

  • 經過裝飾後,函式名稱可能被包裝層的名稱 wrapper 取代,文件字串也可能不再指向原函式。
  • from functools import wraps 匯入標準函式庫提供的工具。
  • 把 @wraps(func) 放在 wrapper 定義上方,可以將原函式的名稱、說明文件等資訊保留下來。
  • 這是撰寫一般裝飾器時很好的習慣,尤其當函式會被文件工具、除錯工具或其他程式檢查時。

七、快速整理

  • 核心概念:裝飾器接收函式,並回傳包裝後的函式。
  • 支援參數:多數通用裝飾器使用 *args、**kwargs。
  • 保留回傳值:用 return func(...) 把原函式的結果傳回。
  • 保留函式資訊:使用 functools.wraps。
  • 常見用途:日誌、計時、權限檢查、快取與輸入驗證。

熟悉這幾個例子後,可以試著把「執行前後顯示訊息」改成記錄每次函式呼叫的參數,或替計時裝飾器加上不同函式的測試。從 wrapper 的進入、原函式的執行,到結果回傳,便能掌握裝飾器的基本運作方式。

沒有留言:

張貼留言