Skip to content

32|装饰器

装饰器是在不修改原函数代码的前提下,给函数添加额外功能的机制。常见的用途有:计时、日志、重试、权限检查、缓存。

一、函数是一等对象

在 Python 中,函数和数字、字符串一样,可以赋值给变量、作为参数传递、作为返回值。

python
def greet(name):
    return f"hello {name}"

say_hi = greet          # 把函数赋给变量
say_hi("Alice")         # 'hello Alice'

函数作为参数:

python
def apply_twice(func, value):
    return func(func(value))

apply_twice(lambda x: x * 2, 5)   # 20

函数作为返回值:

python
def make_multiplier(n):
    def multiply(x):
        return x * n
    return multiply

triple = make_multiplier(3)
triple(5)   # 15

二、最简单的装饰器

装饰器本质上是一个接收函数、返回新函数的高阶函数:

python
def log_call(func):
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} 返回 {result}")
        return result
    return wrapper


def add(a, b):
    return a + b

# 手动应用装饰器
add = log_call(add)
add(2, 3)
# 调用 add
# add 返回 5

三、@ 语法

@ 是应用装饰器的语法糖:

python
@log_call
def add(a, b):
    return a + b

# 等价于:
# add = log_call(add)

四、带参数的装饰器

装饰器本身需要参数时,要嵌套三层函数:

python
def retry(times=3):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for attempt in range(1, times + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as exc:
                    print(f"第 {attempt} 次失败: {exc}")
                    if attempt == times:
                        raise
        return wrapper
    return decorator


@retry(times=3)
def fetch_data():
    ...

执行顺序:retry(times=3) 先调用,返回真正的装饰器 decorator,再用它包装 fetch_data

五、保留元信息

装饰后的函数丢失了原函数的元信息:

python
@log_call
def add(a, b):
    """返回两数之和。"""
    return a + b

print(add.__name__)   # wrapper,不是 add
print(add.__doc__)    # None,不是原 docstring

functools.wraps 修复:

python
from functools import wraps

def log_call(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_call
def add(a, b):
    """返回两数之和。"""
    return a + b

print(add.__name__)   # add
print(add.__doc__)    # 返回两数之和。

@wraps(func) 把原函数的 __name____doc__ 等属性复制到 wrapper 上。

六、类装饰器

装饰器也可以用类实现:

python
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()   # 被调用了 1 次
say_hello()   # 被调用了 2 次

类装饰器适合需要维护状态的场景(如调用次数、缓存内容)。

七、内置装饰器

Python 提供了几个常用装饰器:

python
# @property:把方法变成属性
class Circle:
    @property
    def area(self):
        return 3.14 * self.radius ** 2

# @staticmethod:静态方法,不需要 self
class Math:
    @staticmethod
    def add(a, b):
        return a + b

# @classmethod:类方法,第一个参数是 cls
class Person:
    count = 0

    @classmethod
    def create(cls, name):
        cls.count += 1
        return cls(name)

记忆锚点:装饰器是接收函数、返回新函数的高阶函数;@ 是应用装饰器的语法糖;带参数的装饰器需要三层嵌套;@wraps(func) 保留原函数元信息;类装饰器通过 __call__ 实现,适合维护状态;@property@staticmethod@classmethod 是内置装饰器。