Python 装饰器模式

Python 中的装饰器模式(Decorator Pattern)

装饰器模式是一种结构型设计模式,其核心目的是:
在不改变对象原有接口的情况下,动态地给对象添加额外职责(功能)

形象比喻:就像给礼物包装层层彩纸,每层包装都是一个“装饰”,礼物本身不变,但外观和功能增强了。

装饰器模式的优点

  • 比继承更灵活(可以运行时组合多个装饰)
  • 符合开闭原则:对扩展开放,对修改关闭
  • 支持职责的动态添加和移除
  • 避免子类爆炸(继承会导致类数量指数增长)

Python 中的特殊之处

Python 语言本身内置了装饰器语法 @decorator,这让装饰器模式在 Python 中实现得异常简洁和优雅,甚至可以说是 Python 最常用的设计模式之一。

我们分两种情况讲解:

  1. 函数装饰器(最常见)
  2. 类/对象装饰器(经典 GoF 装饰器模式)

1. 函数装饰器(Python 最 Pythonic 的方式)

用于增强函数功能,常用于日志、计时、权限检查、缓存等。

import time
from functools import wraps

# 装饰器:记录函数执行时间
def timer(func):
    @wraps(func)  # 保留原函数的元信息(如 __name__)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} 执行耗时: {end - start:.4f} 秒")
        return result
    return wrapper

# 装饰器:打印日志
def logger(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 调用 {func.__name__},参数: {args}, {kwargs}")
        return func(*args, **kwargs)
    return wrapper

# 使用装饰器(支持多层装饰)
@timer
@logger
def factorial(n: int) -> int:
    if n == 0:
        return 1
    return n * factorial(n - 1)

# 调用
factorial(10)

输出示例

[2025-12-24 10:00:00] 调用 factorial,参数: (10,), {}
factorial 执行耗时: 0.0001 秒

多层装饰顺序:从下往上执行(@logger 先包裹,再 @timer)。

带参数的装饰器(更高级)

def repeat(times: int):
    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 greet(name):
    print(f"Hello, {name}!")

greet("Python")  # 打印 3 次

2. 类装饰器(经典装饰器模式实现)

用于给对象动态添加职责,更接近 GoF 原意。

from abc import ABC, abstractmethod

# 组件接口(Component)
class Coffee(ABC):
    @abstractmethod
    def cost(self) -> float:
        pass

    @abstractmethod
    def description(self) -> str:
        pass

# 具体组件(Concrete Component)
class SimpleCoffee(Coffee):
    def cost(self) -> float:
        return 5.0

    def description(self) -> str:
        return "Simple Coffee"

# 抽象装饰器(Decorator)
class CoffeeDecorator(Coffee):
    def __init__(self, coffee: Coffee):
        self._coffee = coffee

    def cost(self):
        return self._coffee.cost()

    def description(self):
        return self._coffee.description()

# 具体装饰器:加牛奶
class MilkDecorator(CoffeeDecorator):
    def cost(self):
        return self._coffee.cost() + 2.0

    def description(self):
        return self._coffee.description() + ", Milk"

# 具体装饰器:加糖
class SugarDecorator(CoffeeDecorator):
    def cost(self):
        return self._coffee.cost() + 0.5

    def description(self):
        return self._coffee.description() + ", Sugar"

# 使用(动态组合)
if __name__ == "__main__":
    coffee = SimpleCoffee()
    print(coffee.description(), coffee.cost())  # Simple Coffee 5.0

    coffee_with_milk = MilkDecorator(coffee)
    print(coffee_with_milk.description(), coffee_with_milk.cost())  # + Milk 7.0

    coffee_with_milk_and_sugar = SugarDecorator(MilkDecorator(SimpleCoffee()))
    print(coffee_with_milk_and_sugar.description(), coffee_with_milk_and_sugar.cost())
    # Simple Coffee, Milk, Sugar 7.5

这正是经典装饰器模式的结构:装饰器持有被装饰对象,调用时层层转发并添加行为。


Python 中更简洁的类装饰器方式(函数式)

Python 允许直接用函数装饰类:

def dataclass_like(cls):
    # 简单实现类似 @dataclass 的 __repr__
    def __repr__(self):
        attrs = ', '.join(f"{k}={v!r}" for k, v in self.__dict__.items())
        return f"{cls.__name__}({attrs})"
    cls.__repr__ = __repr__
    return cls

@dataclass_like
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2)
print(p)  # Point(x=1, y=2)

装饰器模式 vs 其他模式对比

模式目的是否改变接口
适配器转换接口改变
装饰器增强功能(保持原接口)不改变
代理控制访问(懒加载、权限等)不改变
组合静态组合对象

实际常见应用场景

  • Web 框架(如 Flask)的 @app.route('/')
  • Django 的 @login_required
  • 缓存:@lru_cache(functools 内置)
  • 日志、权限、事务、性能监控
  • 中间件系统(如 FastAPI 的依赖注入)

注意事项

  • 装饰器顺序很重要(多层时)
  • 使用 @wraps 保留原函数元信息
  • 过多装饰可能影响调试和性能
  • 类装饰器会影响继承和类型检查

装饰器模式是 Python 中使用最频繁的设计模式之一,掌握它能让你写出更优雅、更可维护的代码!

如果你想看更多实战例子(如 Flask 路由装饰器实现、缓存装饰器、权限系统),或者如何实现带状态的装饰器,随时告诉我!

文章已创建 3511

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部