装饰器

基本概念

装饰器是接收函数并返回新函数的高阶函数,通过 @ 语法糖应用。

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("调用前")
        result = func(*args, **kwargs)
        print("调用后")
        return result
    return wrapper
 
@my_decorator
def greet(name):
    print(f"Hello, {name}!")
 
greet("Alice")
# 调用前
# Hello, Alice!
# 调用后

functools.wraps

不使用 wraps 会丢失原函数的 __name____doc__ 等元信息。

from functools import wraps
 
def my_decorator(func):
    @wraps(func)           # 保留原函数元信息
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper
 
@my_decorator
def add(a, b):
    """返回两数之和"""
    return a + b
 
add.__name__   # "add"(不加 wraps 则为 "wrapper")
add.__doc__    # "返回两数之和"

带参数的装饰器

需要额外一层嵌套:工厂函数 → 装饰器 → 包装函数。

from functools import wraps
 
def repeat(n):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(n):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator
 
@repeat(3)
def hello():
    print("Hello!")
 
hello()   # 打印 3 次 "Hello!"

基于类的装饰器

通过实现 __call__ 方法创建可调用对象作为装饰器。

from functools import wraps
 
class Retry:
    def __init__(self, times=3):
        self.times = times
 
    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(self.times):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == self.times - 1:
                        raise
                    print(f"第 {attempt+1} 次失败,重试...")
        return wrapper
 
@Retry(times=3)
def fetch_data():
    ...

实用装饰器示例

计时器

import time
from functools import wraps
 
def timeit(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} 耗时 {elapsed:.4f}s")
        return result
    return wrapper
 
@timeit
def slow_fn():
    time.sleep(0.1)

缓存(手动实现)

from functools import wraps
 
def memoize(func):
    cache = {}
    @wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper
 
@memoize
def fib(n):
    return n if n < 2 else fib(n-1) + fib(n-2)

登录验证

from functools import wraps
 
def login_required(func):
    @wraps(func)
    def wrapper(request, *args, **kwargs):
        if not request.user.is_authenticated:
            raise PermissionError("需要登录")
        return func(request, *args, **kwargs)
    return wrapper

堆叠装饰器

多个装饰器从下往上应用(最靠近函数的先执行)。

@decorator_a
@decorator_b
@decorator_c
def fn():
    pass
 
# 等价于
fn = decorator_a(decorator_b(decorator_c(fn)))

内置装饰器速查

装饰器用途
@property属性访问器
@classmethod类方法,第一参数为 cls
@staticmethod静态方法,无隐式参数
@functools.lru_cache自动 LRU 缓存
@functools.cached_property延迟计算且缓存的属性(3.8+)
@dataclasses.dataclass自动生成 __init__ 等方法
@abstractmethod声明抽象方法

相关链接