Python 裝飾器 (Decorators)
裝飾器是一種設計模式,用來在不修改原函數的情況下,為函數增加額外的功能。
基本概念
裝飾器本質上是一個接受函數作為參數,並回傳新函數的函數:
def my_decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
def say_hello():
print("Hello!")
# 手動套用裝飾器
decorated_func = my_decorator(say_hello)
decorated_func()
輸出:
Before function call
Hello!
After function call
@ 語法
Python 提供 @ 語法來簡化裝飾器的使用:
def my_decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
# 等同於 say_hello = my_decorator(say_hello)
say_hello()
處理函數參數
使用 *args 和 **kwargs 讓裝飾器能處理任意參數的函數:
def my_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"Finished {func.__name__}")
return result
return wrapper
@my_decorator
def add(a, b):
return a + b
@my_decorator
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(add(3, 5))
print(greet("Alice", greeting="Hi"))
保留函數資訊
裝飾器會改變函數的 __name__ 和 __doc__,使用 functools.wraps 來保留原函數的資訊:
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""Wrapper function"""
return func(*args, **kwargs)
return wrapper
@my_decorator
def greet(name):
"""Greet someone"""
return f"Hello, {name}!"
print(greet.__name__) # greet(而不是 wrapper)
print(greet.__doc__) # Greet someone
帶參數的裝飾器
如果裝飾器需要參數,需要再包一層函數:
from functools import wraps
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say_hello():
print("Hello!")
say_hello()
輸出:
Hello!
Hello!
Hello!
常用裝飾器範例
計時裝飾器
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
slow_function() # slow_function took 1.0012 seconds
日誌裝飾器
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@log
def add(a, b):
return a + b
add(3, 5)
輸出:
Calling add with args=(3, 5), kwargs={}
add returned 8
快取裝飾器
from functools import wraps
def cache(func):
cached = {}
@wraps(func)
def wrapper(*args):
if args in cached:
print(f"Cache hit for {args}")
return cached[args]
result = func(*args)
cached[args] = result
return result
return wrapper
@cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10))
Python 內建的
functools.lru_cache 提供更完整的快取功能。重試裝飾器
import time
from functools import wraps
def retry(max_attempts=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
print(f"Attempt {attempts} failed: {e}")
if attempts < max_attempts:
time.sleep(delay)
raise Exception(f"Failed after {max_attempts} attempts")
return wrapper
return decorator
@retry(max_attempts=3, delay=1)
def unreliable_function():
import random
if random.random() < 0.7:
raise Exception("Random failure")
return "Success!"
權限檢查裝飾器
from functools import wraps
def require_auth(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if not user.get("is_authenticated"):
raise PermissionError("Authentication required")
return func(user, *args, **kwargs)
return wrapper
def require_role(role):
def decorator(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if user.get("role") != role:
raise PermissionError(f"Role '{role}' required")
return func(user, *args, **kwargs)
return wrapper
return decorator
@require_auth
@require_role("admin")
def delete_user(user, user_id):
print(f"Deleting user {user_id}")
admin = {"is_authenticated": True, "role": "admin"}
delete_user(admin, 123) # Deleting user 123
多個裝飾器
可以同時套用多個裝飾器,由下往上執行:
@decorator1
@decorator2
@decorator3
def func():
pass
# 等同於
func = decorator1(decorator2(decorator3(func)))
類別裝飾器
裝飾器也可以應用在類別上:
def singleton(cls):
instances = {}
@wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
def __init__(self):
print("Creating database connection")
db1 = Database() # Creating database connection
db2 = Database() # 不會再建立新的
print(db1 is db2) # True
用類別實作裝飾器
class Timer:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
import time
start = time.time()
result = self.func(*args, **kwargs)
end = time.time()
print(f"{self.func.__name__} took {end - start:.4f}s")
return result
@Timer
def slow_function():
import time
time.sleep(1)
slow_function()