Skip to content

装饰器深度解析

装饰器是 Python 最优雅的特性之一,也是框架开发的核心工具。从语法糖到底层原理,彻底掌握它。

装饰器本质

装饰器就是接受函数/类作为参数,返回新函数/类的可调用对象

python
# 这两种写法完全等价
@decorator
def func():
    pass

# 等价于:
def func():
    pass
func = decorator(func)

函数装饰器

最简单的装饰器

python
import functools

def timer(func):
    @functools.wraps(func)  # 保留原函数元信息
    def wrapper(*args, **kwargs):
        import time
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} 耗时 {elapsed:.4f}s")
        return result
    return wrapper

@timer
def slow_function():
    import time
    time.sleep(0.1)

slow_function()  # slow_function 耗时 0.1001s

functools.wraps 的重要性

python
def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

def good_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@bad_decorator
def my_func():
    """我的函数文档"""
    pass

@good_decorator
def my_func2():
    """我的函数文档"""
    pass

print(my_func.__name__)   # wrapper — 元信息丢失!
print(my_func2.__name__)  # my_func2 — 正确保留
print(my_func2.__doc__)   # 我的函数文档

带参数的装饰器

需要多一层嵌套:

python
def retry(max_times=3, exceptions=(Exception,)):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_times):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    if attempt == max_times - 1:
                        raise
                    print(f"第 {attempt+1} 次失败: {e},重试中...")
        return wrapper
    return decorator

@retry(max_times=3, exceptions=(ConnectionError,))
def fetch_data(url):
    # 模拟网络请求
    raise ConnectionError("网络超时")

# fetch_data("http://example.com")

类装饰器

用类实现装饰器(有状态)

python
class CallCounter:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"第 {self.count} 次调用 {self.func.__name__}")
        return self.func(*args, **kwargs)

@CallCounter
def greet(name):
    return f"Hello, {name}!"

greet("Alice")  # 第 1 次调用 greet
greet("Bob")    # 第 2 次调用 greet
print(greet.count)  # 2

装饰类

python
def singleton(cls):
    """单例模式装饰器"""
    instances = {}
    @functools.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 DatabaseConnection:
    def __init__(self):
        print("创建数据库连接")

db1 = DatabaseConnection()  # 创建数据库连接
db2 = DatabaseConnection()  # 不再打印
print(db1 is db2)  # True

实用装饰器库

functools.lru_cache — 缓存

python
from functools import lru_cache, cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(100))  # 极快,无递归爆栈
print(fibonacci.cache_info())  # CacheInfo(hits=98, misses=101, ...)

# Python 3.9+ 无限缓存
@cache
def factorial(n):
    return n * factorial(n-1) if n else 1

functools.cached_property — 惰性属性

python
class Circle:
    def __init__(self, radius):
        self.radius = radius

    @functools.cached_property
    def area(self):
        import math
        print("计算面积...")
        return math.pi * self.radius ** 2

c = Circle(5)
print(c.area)  # 计算面积... 78.53...
print(c.area)  # 直接返回缓存,不再计算

装饰器叠加顺序

python
def decorator_a(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("A 前")
        result = func(*args, **kwargs)
        print("A 后")
        return result
    return wrapper

def decorator_b(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("B 前")
        result = func(*args, **kwargs)
        print("B 后")
        return result
    return wrapper

@decorator_a
@decorator_b
def hello():
    print("Hello!")

hello()
# 输出:
# A 前
# B 前
# Hello!
# B 后
# A 后

叠加顺序:从下到上应用,从外到内执行。@a @b func 等价于 a(b(func))

实战:权限控制装饰器

python
from functools import wraps

def require_permission(*permissions):
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            user_perms = getattr(user, 'permissions', set())
            missing = set(permissions) - user_perms
            if missing:
                raise PermissionError(f"缺少权限: {missing}")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

class User:
    def __init__(self, name, permissions):
        self.name = name
        self.permissions = set(permissions)

@require_permission('admin', 'write')
def delete_user(current_user, target_id):
    print(f"{current_user.name} 删除用户 {target_id}")

admin = User("Alice", ['admin', 'write', 'read'])
guest = User("Bob", ['read'])

delete_user(admin, 42)   # Alice 删除用户 42
# delete_user(guest, 42) # PermissionError: 缺少权限: {'admin', 'write'}

实战:类型检查装饰器

python
import inspect
from functools import wraps

def type_check(func):
    hints = func.__annotations__
    sig = inspect.signature(func)

    @wraps(func)
    def wrapper(*args, **kwargs):
        bound = sig.bind(*args, **kwargs)
        bound.apply_defaults()
        for param_name, value in bound.arguments.items():
            if param_name in hints:
                expected = hints[param_name]
                if not isinstance(value, expected):
                    raise TypeError(
                        f"参数 '{param_name}' 期望 {expected.__name__},"
                        f"得到 {type(value).__name__}"
                    )
        return func(*args, **kwargs)
    return wrapper

@type_check
def add(a: int, b: int) -> int:
    return a + b

print(add(1, 2))    # 3
# add(1, "2")       # TypeError: 参数 'b' 期望 int,得到 str

框架中的装饰器

FastAPI 的 @app.get()、Flask 的 @app.route()、pytest 的 @pytest.fixture 都是装饰器的典型应用。理解装饰器原理,读框架源码就不再神秘。

本站内容由 褚成志 整理编写,仅供学习参考