首页 文章 文章详情

Python 常用装饰器及应用场景

来源:本站 {{likeCount}} {{commentCount}} 评论 2026-03-02 12:27:31

Python 装饰器(Decorator)是一种强大的设计模式,它允许你在不修改原有函数或类代码的前提下,动态地为其添加新功能。装饰器本质上是一个高阶函数(接收函数作为参数并返回新函数),通过 @ 语法糖简化调用。

以下是 Python 中常用的装饰器及其典型应用场景,分为内置装饰器自定义常用装饰器两类:

一、Python 内置装饰器

这些是 Python 语言本身提供的装饰器,主要用于类和方法的定义。

装饰器作用应用场景
@property将方法转换为只读属性,支持 getter/setter/deleter封装私有变量,实现数据验证、计算属性(如根据身高体重自动计算 BMI)
@classmethod将方法绑定到类而非实例,第一个参数为 cls实现备选构造函数(如从字符串解析创建对象)、操作类变量
@staticmethod定义静态方法,不需要 selfcls 参数工具函数,逻辑上属于类但不需要访问类或实例状态
@dataclass (Py3.7+)自动生成 __init__, __repr__, __eq__ 等方法快速定义数据容器类,减少样板代码
@abstractmethod定义抽象方法,强制子类实现构建接口规范,用于抽象基类(ABC)
# 示例:@property 实现数据验证
class Person:
    def __init__(self, age):
        self._age = None
        self.age = age  # 触发 setter

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if value < 0:
            raise ValueError("年龄不能为负数")
        self._age = value


二、自定义常用装饰器及应用场景

在实际开发中,我们常编写自定义装饰器来处理横切关注点(Cross-Cutting Concerns)。

1. 日志记录 (@log)

  • 场景:记录函数的调用时间、参数、返回值或异常,便于调试和审计。
  • 代码示例
import functools
import logging

def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        logging.info(f"调用 {func.__name__}, 参数: {args}, {kwargs}")
        try:
            result = func(*args, **kwargs)
            logging.info(f"{func.__name__} 返回: {result}")
            return result
        except Exception as e:
            logging.error(f"{func.__name__} 抛出异常: {e}")
            raise
    return wrapper

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

2. 性能计时 (@timer)

  • 场景:测量函数执行时间,用于性能分析和优化。
  • 代码示例
import time
import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__} 执行耗时: {end - start:.6f} 秒")
        return result
    return wrapper

3. 重试机制 (@retry)

  • 场景:处理网络请求、数据库连接等不稳定操作,失败时自动重试。
  • 代码示例
import time
import functools

def retry(times=3, delay=1):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for i in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if i == times - 1:
                        raise
                    print(f"重试 {i+1}/{times}: {e}")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(times=3, delay=2)
def fetch_data():
    # 模拟不稳定操作
    raise ConnectionError("网络波动")

4. 缓存结果 (@cache / @lru_cache)

  • 场景:对于计算密集型或 IO 密集型且输入输出确定的函数,避免重复计算。
  • 注意:Python 3.9+ 提供了内置的 @cache,旧版本可用 functools.lru_cache
  • 代码示例
from functools import lru_cache

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

5. 权限校验 (@login_required / @permission)

  • 场景:Web 开发中(如 Flask/Django),检查用户是否登录或拥有特定权限。
  • 代码示例(简化版):
def login_required(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        current_user = get_current_user() # 假设获取当前用户的函数
        if not current_user.is_authenticated:
            raise PermissionError("未登录")
        return func(*args, **kwargs)
    return wrapper

6. 参数验证 (@validate)

  • 场景:确保传入函数的参数符合特定条件(如类型、范围)。
  • 代码示例
def validate_positive(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        for arg in args:
            if isinstance(arg, (int, float)) and arg <= 0:
                raise ValueError("参数必须为正数")
        return func(*args, **kwargs)
    return wrapper


三、编写装饰器的最佳实践

  1. 使用 functools.wraps:务必在内部 wrapper 函数上使用 @functools.wraps(func)。这会保留原函数的元数据(如 __name__, __doc__),否则调试和文档生成工具会显示 wrapper 的信息而不是原函数信息。
  2. 支持任意参数:定义 wrapper 时使用 *args**kwargs,确保装饰器可以应用于任何签名的函数。
  3. 带参数的装饰器:如果装饰器本身需要参数(如 @retry(times=3)),需要三层嵌套函数(外层接收装饰器参数,中间层接收函数,内层执行逻辑)。
  4. 类装饰器:除了函数装饰器,还可以定义类装饰器(实现 __call__ 方法的类),用于更复杂的状态管理。

总结

装饰器是 Python“优雅”和“简洁”哲学的体现。通过合理使用装饰器,可以将业务逻辑通用功能(如日志、监控、安全)分离,使代码更易维护、复用和测试。

相关评论
发表
暂无相关评论...
{{item.userName}} {{item.dateDescription}}
{{item.likeCount}} 回复
{{item.content}}
{{child.userName}}@{{child.atUserName}} {{child.content}}
{{child.dateDescription}}
{{child.likeCount}} 回复