源本科技 | 码上会

Python 装饰器

2026/01/18
11
0

学习目标

  • 理解装饰器的基本原理:函数增强而不修改原代码

  • 掌握带参数函数的装饰器写法(使用 *args**kwargs

  • 了解函数作为“一等公民”和高阶函数在装饰器中的作用

  • 熟悉函数装饰器、方法装饰器、类装饰器的使用场景

  • 掌握 Python 内置装饰器 @staticmethod@classmethod@property

  • 能够实现多装饰器链式调用并理解执行顺序


什么是装饰器

装饰器是 Python 中一种优雅的元编程工具,用于在不修改原始函数代码的前提下,动态地为其添加新功能(如日志、权限校验、缓存等)。从本质上讲,装饰器是一个接受函数作为参数并返回新函数的高阶函数

在函数前后打印消息

def decorator(func):
    def wrapper():
        print("Before calling the function.")
        func()
        print("After calling the function.")
    return wrapper

@decorator
def greet():
    print("Hello, World!")

greet()

输出:

Before calling the function.
Hello, World!
After calling the function.

等价写法:
greet = decorator(greet)
@decorator 是语法糖,使代码更简洁清晰。


支持任意参数的

实际函数通常带有参数,因此装饰器的内部函数需使用 *args**kwargs 以兼容所有函数签名。

def log_execution(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_execution
def add(a, b):
    return a + b

print(add(5, 3))

输出:

Calling add with args=(5, 3), kwargs={}
add returned 8
8

关键:wrapper 必须能接收任意位置参数和关键字参数,并将它们原样传递给原函数。


函数是一等公民

Python 中的函数具有以下特性,这是装饰器得以实现的基础:

  1. 可赋值给变量

  2. 可作为参数传递

  3. 可作为函数返回值

  4. 可存储在数据结构中

# 1. 赋值
def say_hello(name):
    return f"Hello, {name}!"
greet = say_hello

# 2. 作为参数
def apply(func, value):
    return func(value)

# 3. 作为返回值
def make_multiplier(n):
    def multiply(x):
        return x * n
    return multiply

double = make_multiplier(2)
print(double(5))  # 10

这些特性使得函数可以像普通对象一样被操作,为装饰器提供了可能。


高阶函数与装饰器

高阶函数是指:

  • 接受一个或多个函数作为输入,

  • 返回一个函数作为输出

装饰器正是典型的高阶函数:它接收一个函数,返回一个增强后的新函数。

def apply_twice(func):
    def inner(x):
        return func(func(x))
    return inner

def increment(x):
    return x + 1

double_increment = apply_twice(increment)
print(double_increment(3))  # 5 (3 → 4 → 5)

装饰器的类型

1. 函数装饰器(最常见)

用于增强普通函数的行为。

def timer(func):
    import time
    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():
    import time
    time.sleep(0.1)

slow_function()

2. 方法装饰器

用于类中的方法,需处理 self 参数。

def method_logger(func):
    def wrapper(self, *args, **kwargs):
        print(f"Calling method: {func.__name__}")
        return func(self, *args, **kwargs)
    return wrapper

class Calculator:
    @method_logger
    def add(self, a, b):
        return a + b

calc = Calculator()
print(calc.add(2, 3))

输出:

Calling method: add
5

3. 类装饰器

作用于整个类,可用于添加属性、方法或修改类行为。

def add_class_info(cls):
    cls.class_name = cls.__name__
    cls.module = cls.__module__
    return cls

@add_class_info
class User:
    pass

print(User.class_name)  # User
print(User.module)      # __main__

内置装饰器

@staticmethod

定义不依赖实例或类状态的方法,无需 selfcls

class MathUtils:
    @staticmethod
    def square(x):
        return x * x

print(MathUtils.square(4))  # 16

@classmethod

定义操作类本身的方法,第一个参数是 cls(类对象)。

class Person:
    species = "Homo sapiens"
    
    @classmethod
    def get_species(cls):
        return cls.species

print(Person.get_species())  # Homo sapiens

常用于替代构造函数(工厂方法):

class Date:
    def __init__(self, year, month, day):
        self.year, self.month, self.day = year, month, day
    
    @classmethod
    def from_string(cls, date_str):
        y, m, d = map(int, date_str.split('-'))
        return cls(y, m, d)

d = Date.from_string("2025-09-22")

@property

将方法变为只读或可控的属性,支持封装。

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        return self._radius
    
    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value
    
    @property
    def area(self):
        return 3.14159 * self._radius ** 2

c = Circle(5)
print(c.radius)  # 5
print(c.area)    # 78.53975
c.radius = 10    # 触发 setter

多装饰器链式调用

可以对一个函数应用多个装饰器,执行顺序从下到上(靠近函数的先执行)。

def bold(func):
    def wrapper():
        return f"<b>{func()}</b>"
    return wrapper

def italic(func):
    def wrapper():
        return f"<i>{func()}</i>"
    return wrapper

@bold
@italic
def text():
    return "Hello"

print(text())  # <b><i>Hello</i></b>

等价于:
text = bold(italic(text))

顺序很重要!交换 @bold@italic 会改变 HTML 嵌套结构。


实际应用场景

场景

示例

日志记录

记录函数调用时间、参数、返回值

身份验证

Web 框架中限制未登录用户访问(如 Flask 的 @login_required

缓存

使用 @functools.lru_cache 缓存计算结果

重试机制

网络请求失败时自动重试

性能监控

统计函数执行时间或调用次数

输入验证

检查参数合法性


重点总结

  • 装饰器是不侵入原函数代码即可扩展其功能的强大工具

  • 核心结构:外层函数接收原函数,内层 wrapper 执行增强逻辑

  • 必须使用 *args, **kwargs 保证通用性

  • @decoratorfunc = decorator(func) 的语法糖

  • 多装饰器按从下到上的顺序包装

  • 内置装饰器 @staticmethod@classmethod@property 是面向对象编程的重要工具

  • 装饰器广泛应用于框架开发、中间件、AOP(面向切面编程)等场景


思考题

  1. 如果一个装饰器没有正确返回 wrapper 函数,会发生什么?如何避免?

  2. 使用 functools.wraps 装饰 wrapper 有什么好处?请举例说明。

  3. 尝试编写一个装饰器 @retry(max_attempts=3),当被装饰的函数抛出异常时自动重试最多 3 次。