五大实用 Python 装饰器模式,助力你的代码更高效、更优雅

如果你已经用 Python 编程有一段时间,可能已经见过并使用过装饰器。虽然许多开发者都了解装饰器的基础用法,但收集一些有用且可复用的装饰器模式,可以显著提升你的代码质量和开发效率。

本文将介绍五种值得加入你的工具箱的装饰器模式。每种模式都附有示例实现和实际应用案例。让我们开始吧!

Custom Python Decorator Patterns Worth Copy-Pasting Forever

🔗 点击查看 GitHub 上的完整代码


什么是装饰器?

在深入讲解之前,让我们快速回顾一下什么是装饰器。归根结底,装饰器是一种用于在不修改原始函数(或类)源码的情况下,增强其功能的函数。装饰器的一般用法如下:

@decorator
def function():
    pass

这实际上等同于:

function = decorator(function)

现在,让我们进入精彩内容吧。


1. 记忆化(Memoization)

假设你有一个函数,需要根据输入做一些耗时的计算,比如复杂的数学运算、大型数据集的处理、API 调用等。每次都重复计算其实很浪费资源。

下面是一个简单的记忆化装饰器:

def memoize(func):
    """根据参数缓存函数的返回值。"""
    cache = {}

    def wrapper(*args, **kwargs):
        # 创建唯一标识本次调用的 key
        key = str(args) + str(kwargs)
        if key not in cache:
            cache[key] = func(*args, **kwargs)
        return cache[key]

    return wrapper

示例:斐波那契数列

假设你要递归计算斐波那契数列,可以这样记忆化:

@memoize
def fibonacci(n):
    """计算第 n 个斐波那契数。"""
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# 没有记忆化时会很慢
result = fibonacci(50)
print(f"第 50 个斐波那契数是 {result}")

输出:

第 50 个斐波那契数是 12586269025

没有记忆化时,fibonacci(50) 由于递归调用会非常慢;而加上记忆化后,几乎瞬间完成计算。

适用场景: 当你需要重复处理相同输入且函数结果不会变化时,适合采用这种模式。


2. 日志记录(Logging)

你不总是希望在函数里到处插入 print 或 debug 语句,尤其是想在多个函数中实现统一、规范的日志记录时。

这个装饰器可以记录函数调用的相关信息,非常适合调试和监控:

import logging
import functools

def log_calls(func=None, level=logging.INFO):
    """记录函数调用的参数和返回值。"""

    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            args_str = ", ".join([str(a) for a in args])
            kwargs_str = ", ".join([f"{k}={v}" for k, v in kwargs.items()])
            all_args = f"{args_str}{', ' if args_str and kwargs_str else ''}{kwargs_str}"

            logging.log(level, f"调用 {func.__name__}({all_args})")
            result = func(*args, **kwargs)
            logging.log(level, f"{func.__name__} 返回 {result}")

            return result
        return wrapper

    # 兼容 @log_calls 和 @log_calls(level=logging.DEBUG) 两种用法
    if func is None:
        return decorator
    return decorator(func)

示例:日志记录函数调用

logging.basicConfig(level=logging.INFO)

@log_calls
def divide(a, b):
    return a / b

# 这会自动记录调用和返回值
result = divide(10, 2)

# 还可以自定义日志级别
@log_calls(level=logging.DEBUG)
def multiply(a, b):
    return a * b

result = multiply(5, 4)

适用场景: 适用于小型脚本、API 端点或批量作业调试时,统一记录函数调用信息。


3. 执行时间统计(Timing Execution)

有时你想快速了解函数运行所需的时间,尤其是涉及数据库、文件解析或模型训练等场景。

import time
import functools

def timeit(func):
    """统计并打印函数的执行时间。"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()

        print(f"{func.__name__} 执行耗时 {end_time - start_time:.4f} 秒")
        return result

    return wrapper

示例:统计函数运行时间

@timeit
def slow_function():
    """一个故意运行缓慢的函数用于演示。"""
    total = 0
    for i in range(10000000):
        total += i
    return total

result = slow_function()  # 会打印运行耗时

输出:

slow_function 执行耗时 0.5370 秒

适用场景: 这种装饰器适用于对简单函数做基准测试,帮助你发现优化空间。


4. 自动重试(Retry on Failure)

在和外部服务交互或执行不稳定操作时,失败后自动重试可以让代码更健壮。

def retry(max_attempts=3, delay_seconds=1, backoff_factor=2, exceptions=(Exception,)):
    """在指定异常时自动重试函数。"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            current_delay = delay_seconds

            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    attempts += 1
                    if attempts == max_attempts:
                        logging.error(f"尝试 {attempts} 次后失败。最后的错误: {e}")
                        raise

                    logging.warning(
                        f"第 {attempts} 次尝试失败,错误:{e}。{current_delay} 秒后重试..."
                    )

                    time.sleep(current_delay)
                    current_delay *= backoff_factor

        return wrapper
    return decorator

这个 retry 装饰器会在指定异常发生时自动重试函数,最多尝试 max_attempts 次。它实现了指数退避策略,即每次重试的间隔会倍增。你可以自定义最大重试次数、初始延迟、退避倍数和捕捉的异常类型。重试时会记录警告日志,最终失败时会记录错误并抛出最后一次异常。

示例:带重试机制的 API 查询

(确保已安装 requests 库)

import random
import requests

@retry(max_attempts=5, delay_seconds=1, exceptions=(requests.RequestException,))
def fetch_data(url):
    """带重试逻辑的 API 数据获取。"""
    response = requests.get(url, timeout=2)
    response.raise_for_status()  # 4XX/5XX 响应抛出异常
    return response.json()

# 失败时会最多重试 5 次
try:
    data = fetch_data('https://api.example.com/data')
    print("数据获取成功!")
except Exception as e:
    print(f"所有重试均失败:{e}")

适用场景: 适用于网络请求、不稳定 API 或任何 IO 密集型操作。


5. 输入验证(Input Validation)

你可以用装饰器自动验证函数输入,而无需让函数主体变得臃肿。

下面这个装饰器用于确保所有输入参数都是正整数:

def validate_positive_ints(func):
    def wrapper(*args):
        for arg in args:
            if not isinstance(arg, int) or arg <= 0:
                raise ValueError(f"{arg} 必须为正整数")
        return func(*args)
    return wrapper

示例:验证正整数输入

@validate_positive_ints
def calculate_area(length, width):
    return length * width

print(calculate_area(5, 10))      # 正常输出 50
print(calculate_area(-1, 10))     # 抛出 ValueError

输出:

50

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)

 in ()
      4 
      5 print(calculate_area(5, 10))
----> 6 print(calculate_area(-1, 10))

 in wrapper(*args)
      3         for arg in args:
      4             if not isinstance(arg, int) or arg <= 0:
----> 5                 raise ValueError(f"{arg} 必须为正整数")
      6         return func(*args)
      7     return wrapper

ValueError: -1 必须为正整数

适用场景: 适合用于数据处理管道和用户输入的参数校验。


总结

可以把装饰器看作是为函数“加装”额外行为的优雅工具。我们介绍的五种模式 —— 记忆化、日志记录、时间统计、自动重试和输入验证 —— 是我日常开发中的常用技巧。

将这些装饰器模式加入你的项目(或者更好地,将它们收集成一个实用工具模块),你会写出更简洁、可维护性更高的代码,也能节省大量重复劳动。

不妨思考一下,代码中哪些功能可以抽象成装饰器,并在全项目内统一应用。当你习惯用这种思路审视代码时,会发现装饰器的用武之地无处不在。

你最喜欢哪一种装饰器模式?你有没有自己写过、不可或缺的专属装饰器?欢迎一起交流分享!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值