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()