
Python系列的筆記到這邊就差不多要告個段落,我介紹了基礎語法與進階語法的功能與使用方式。基礎語法對於初學者來說是滿容易理解而且能快速上手的部分,進階語法則是初學者的噩夢,常常讓人難以理解也不知道怎麼使用,不過它卻是讓大家提升程式設計能力的好工具。 一開始大家不需要去死記硬背這些進階語法,只需要大概有個印象即可,以後在開發時就可能突然想到有個適合現在情境或任務的進階技巧可以使用。
✨ 裝飾器 (Decorators)
裝飾器在Python中是一種很特別的存在,學習它的時候很難理解,使用的時候卻很方便,尤其是在大型專案上常常可以在程式中看到它的存在。 當你想讓原本函式新增其他功能,又不想更動原本函式時,裝飾器就是一種強大而靈活的工具,只是對於初學者來說真的非常抽象難以理解。因此只希望大家可以先知道什麼是裝飾器以及它可以做到哪些事情,未來在開發過程中有機會可以用上。
由於它允許開發者在 不改變函數定義 的情況下,動態地增加或修改函數的行為,因此裝飾器讓程式的結構更加靈活且模組化,非常適合用於擴展函數功能,例如日誌記錄、驗證、計時等。
💡簡單來說,我們可以把裝飾器理解為一種函數專用的外掛!
基本概念: 語法糖(@)
- 裝飾器是一個函數,接受一個函數作為參數,並返回一個新的函數。
- 使用
@decorator_name
語法糖(syntactic sugar)應用裝飾器。
為什麼需要裝飾器?
- 增加函數的功能:在不修改原始函數代碼的前提下,動態在外層添加新的行為。
- 程式碼重用:可將常見的功能(如日誌記錄、權限檢查、計時等)封裝成裝飾器,讓多個函數共享邏輯。
- 程式碼清晰與分層:裝飾器使業務邏輯與輔助功能(如錯誤處理等)分離,提高程式可讀性。
基本特性:函數作為參數與回傳值
裝飾器的核心基於 Python 的函數是一等公民(First-Class Functions),表示函數可以:
- 作為參數傳遞
- 作為回傳值返回
手動模擬裝飾器功能
# 定義函數作為參數
def greet(func):
def wrapper():
print("Before calling the function...")
func() # 呼叫傳入的函數
print("After calling the function...")
return wrapper
def say_hello():
print("Hello!")
# 使用裝飾器
decorated = greet(say_hello)
decorated()
# 輸出
Before calling the function...
Hello!
After calling the function...
建立簡單裝飾器
def my_decorator(func):
def wrapper():
print("執行裝飾器的邏輯(開始)")
func() # 執行被裝飾的函數
print("執行裝飾器的邏輯(結束)")
return wrapper
@my_decorator
def my_function():
print("執行原始函數的邏輯")
my_function()
# 輸出
執行裝飾器的邏輯(開始)
執行原始函數的邏輯
執行裝飾器的邏輯(結束)
裝飾帶參數的函數
def my_decorator(func):
def wrapper(*args, **kwargs): # 接收任意參數
print("開始執行裝飾器")
result = func(*args, **kwargs) # 執行原始函數
print("結束執行裝飾器")
return result
return wrapper
@my_decorator
def add_numbers(a, b):
print(f"總和為:{a + b}")
return a + b
result = add_numbers(5, 10)
print(f"回傳結果:{result}")
# 輸出
開始執行裝飾器
總和為:15
結束執行裝飾器
回傳結果:15
支援裝飾器接受參數
- 當需要讓裝飾器本身可配置時,可使用多一層嵌套的函數
def repeat(times): # 接收參數的裝飾器
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3) # 重複執行 3 次
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")
# 輸出
Hello, Alice!
Hello, Alice!
Hello, Alice!
類裝飾器
- 除了函數形式的裝飾器,也可以使用類作為裝飾器
class CountCalls:
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)
@CountCalls
def say_hello():
print("Hello!")
say_hello()
say_hello()
# 輸出
函數 say_hello 已被調用 1 次
Hello!
函數 say_hello 已被調用 2 次
Hello!
嵌套多個裝飾器
- 當使用多個裝飾器時,它們的執行順序是從下到上(內到外)
- 相當於:decorator1(decorator2(say_hello))
def decorator1(func):
def wrapper(*args, **kwargs):
print("執行 decorator1")
return func(*args, **kwargs)
return wrapper
def decorator2(func):
def wrapper(*args, **kwargs):
print("執行 decorator2")
return func(*args, **kwargs)
return wrapper
@decorator1
@decorator2
def say_hello():
print("Hello, World!")
say_hello()
# 輸出
執行 decorator1
執行 decorator2
Hello, World!
保留函數meta data
- 裝飾器會導致函數的原始資訊丟失,我們可以使用 functools.wraps 來解決這個問題
丟失meta data
def my_decorator(func):
def wrapper(*args, **kwargs):
print("開始執行裝飾器")
result = func(*args, **kwargs)
print("結束執行裝飾器")
return result
return wrapper
@my_decorator
def add_numbers(a, b):
"""計算兩個數字的和"""
return a + b
print(add_numbers.__name__) # 輸出: wrapper
print(add_numbers.__doc__) # 輸出: None
保留meta data
from functools import wraps
def my_decorator(func):
@wraps(func) # 保留原函數的元數據
def wrapper(*args, **kwargs):
print("開始執行裝飾器")
result = func(*args, **kwargs)
print("結束執行裝飾器")
return result
return wrapper
@my_decorator
def add_numbers(a, b):
"""計算兩個數字的和"""
return a + b
print(add_numbers.__name__) # 輸出: add_numbers
print(add_numbers.__doc__) # 輸出: 計算兩個數字的和
優點
裝飾器充分發揮了 Python 函數作為一等公民的優勢,允許在現有代碼的基礎上靈活擴展功能。通過裝飾器的應用,我們可以改善程式結構、優化開發效率,並提升函數的靈活性與可用性。

適用情境
裝飾器作為一種外掛存在,可以在各種既有的函數上新增額外的功能,因此使用範圍非常廣泛。
