如果你已经用 Python 编程有一段时间,可能已经见过并使用过装饰器。虽然许多开发者都了解装饰器的基础用法,但收集一些有用且可复用的装饰器模式,可以显著提升你的代码质量和开发效率。
本文将介绍五种值得加入你的工具箱的装饰器模式。每种模式都附有示例实现和实际应用案例。让我们开始吧!
🔗 点击查看 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 必须为正整数
适用场景: 适合用于数据处理管道和用户输入的参数校验。
总结
可以把装饰器看作是为函数“加装”额外行为的优雅工具。我们介绍的五种模式 —— 记忆化、日志记录、时间统计、自动重试和输入验证 —— 是我日常开发中的常用技巧。
将这些装饰器模式加入你的项目(或者更好地,将它们收集成一个实用工具模块),你会写出更简洁、可维护性更高的代码,也能节省大量重复劳动。
不妨思考一下,代码中哪些功能可以抽象成装饰器,并在全项目内统一应用。当你习惯用这种思路审视代码时,会发现装饰器的用武之地无处不在。
你最喜欢哪一种装饰器模式?你有没有自己写过、不可或缺的专属装饰器?欢迎一起交流分享!