📢 转载信息
原文链接:https://machinelearningmastery.com/7-python-decorator-tricks-to-write-cleaner-code/
原文作者:Jason Brownlee
Python 装饰器是一种非常强大的工具,它可以让你在不修改函数源代码的情况下,为其添加额外的功能。掌握这些技巧,可以帮助你编写出更简洁、更具可读性的 Python 代码。
本文将介绍 7 个实用的 Python 装饰器技巧,助你提升代码质量。
1. 装饰器基础回顾
在深入技巧之前,我们先快速回顾一下装饰器的基本概念。装饰器本质上是一个函数,它接收另一个函数作为参数,返回一个新的函数,这个新函数会包装原函数的功能。
下面是一个最简单的装饰器示例:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("在函数执行前执行...")
result = func(*args, **kwargs)
print("在函数执行后执行...")
return result
return wrapper
@my_decorator
def say_hello():
print("Hello!")
# 调用时,say_hello() 实际上执行的是 wrapper 函数
say_hello()
输出结果:
在函数执行前执行... Hello! 在函数执行后执行...
2. 装饰器进阶:传递参数的装饰器
有时你需要装饰器本身也能接收参数。这需要我们在装饰器外再包裹一层函数,用于接收装饰器参数,并返回一个标准的装饰器函数。
def repeat(num_times):
def decorator_repeat(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
效果:函数 `greet` 会被执行 3 次。
3. 使用 `functools.wraps` 保持函数元数据
当使用装饰器时,被装饰的函数会丢失其原始的名称 (`__name__`)、文档字符串 (`__doc__`) 等元数据。这是因为装饰器返回的是内部的 `wrapper` 函数。
使用 `functools.wraps` 可以解决这个问题。它是一个内置的装饰器,用于将原始函数的元数据复制到包装函数上。
import functools
def log_calls(func):
@functools.wraps(func) # 关键:添加此行
def wrapper(*args, **kwargs):
print(f"Calling function: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_calls
def calculate_sum(a, b):
"""计算两个数的和。"""
return a + b
print(calculate_sum.__name__) # 输出: calculate_sum (没有 @wraps 会输出 wrapper)
print(calculate_sum.__doc__) # 输出: 计算两个数的和。
4. 类作为装饰器:实现状态管理
虽然函数是装饰器的常见形式,但使用类也可以实现装饰器。当你的装饰器需要维护内部状态(比如计数器、缓存等)时,使用类会更加清晰和方便。类装饰器需要实现 `__init__` 和 `__call__` 方法。
- `__init__(self, func)`:接收被装饰的函数作为参数。
- `__call__(self, *args, **kwargs)`:在函数被调用时执行,接收函数的实际参数。
class CounterDecorator:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} has been called {self.count} times.")
return self.func(*args, **kwargs)
@CounterDecorator
def process_data(data):
print(f"Processing: {data}")
process_data("Set A")
process_data("Set B")
5. 装饰器栈:同时应用多个装饰器
Python 允许你在一个函数上堆叠多个装饰器。装饰器的应用顺序是从下往上(**先应用最下面的装饰器,再应用上面的装饰器**)。
@decorator_b
@decorator_a
def my_function():
pass
这等同于执行:`my_function = decorator_b(decorator_a(my_function))`。
6. 装饰器链中的参数传递与返回
在装饰器链中,确保每个包装器都能正确地接收和传递参数,并返回最终结果至关重要。如果一个包装器不返回 `func(*args, **kwargs)` 的结果,那么后续的装饰器将接收到错误的值(通常是 `None`)。
最佳实践: 始终使用 `*args` 和 `**kwargs` 来接收所有位置和关键字参数,并确保返回函数调用的结果。
7. 编写可重用的、通用的装饰器
为了最大化装饰器的复用性,它们应该尽量做到“纯净”(Pure),即减少对全局状态的依赖。如果装饰器需要配置,最好通过装饰器参数(如技巧 2 所示)来提供配置,而不是硬编码在装饰器内部。
一个通用且干净的装饰器应该只做一件事,并且做好。例如,一个专门用于计时执行时间的装饰器:
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
t1 = time.time()
result = func(*args, **kwargs)
t2 = time.time()
print(f'{func.__name__} took {t2 - t1:.4f} seconds.')
return result
return wrapper
@timer
def slow_operation(n):
time.sleep(n)
slow_operation(0.5)
总结
Python 装饰器是编写简洁、模块化代码的利器。通过掌握参数传递、`functools.wraps` 的使用,以及了解函数装饰器和类装饰器的应用场景,你可以更有效地增强代码的功能,同时保持代码的优雅和可维护性。
🚀 想要体验更好更全面的AI调用?
欢迎使用青云聚合API,约为官网价格的十分之一,支持300+全球最新模型,以及全球各种生图生视频模型,无需翻墙高速稳定,小白也可以简单操作。
青云聚合API官网https://api.qingyuntop.top
支持全球最新300+模型:https://api.qingyuntop.top/pricing
详细的调用教程及文档:https://api.qingyuntop.top/about
评论区