Python 装饰器的 5 个实用技巧,让你的代码更优雅
在 Python 编程中,装饰器(Decorator)是一个强大而优雅的工具。很多初学者对装饰器的理解停留在@staticmethod 或@classmethod 这类内置装饰器上,但实际上,自定义装饰器能够极大地提升代码的可读性和可维护性。
本文将通过 5 个实用的装饰器技巧,带你深入理解装饰器的工作原理,并学会如何在实际项目中灵活运用。所有代码示例都是原创编写,可以直接在你的项目中使用。
技巧一:带参数的装饰器
最基础的装饰器不带参数,但实际工作中我们经常需要传递参数给装饰器。下面是一个可以重复执行函数的装饰器:
import functools
from typing import Callable
def repeat(times: int):
"""让函数重复执行指定次数的装饰器"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
results = []
for i in range(times):
print(f"第 {i + 1}/{times} 次执行")
result = func(*args, **kwargs)
results.append(result)
return results
return wrapper
return decorator
# 使用示例
@repeat(times=3)
def greet(name: str) -> str:
return f"Hello, {name}!"
# 调用
results = greet("World")
print(results) # ['Hello, World!', 'Hello, World!', 'Hello, World!']
这个装饰器的关键在于多层嵌套:最外层接收参数,中间层接收函数,最内层执行实际逻辑。使用 functools.wraps 可以保留原函数的元信息。
技巧二:计时装饰器
性能分析是开发中的常见需求。下面这个装饰器可以测量函数的执行时间:
import functools
import time
from typing import Callable
def timer(unit: str = "seconds"):
"""测量函数执行时间的装饰器"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
elapsed = end - start
if unit == "milliseconds":
elapsed *= 1000
unit_symbol = "ms"
elif unit == "microseconds":
elapsed *= 1000000
unit_symbol = "μs"
else:
unit_symbol = "s"
print(f"{func.__name__} 执行时间:{elapsed:.4f} {unit_symbol}")
return result
return wrapper
return decorator
# 使用示例
@timer(unit="milliseconds")
def slow_function():
time.sleep(0.5)
return "完成"
slow_function() # 输出:slow_function 执行时间:500.xxxx ms
使用 time.perf_counter() 比 time.time() 更精确,适合性能测试。通过 unit 参数可以灵活选择时间单位。
技巧三:缓存装饰器(记忆化)
对于计算密集型且结果可预测的函数,缓存之前的计算结果可以显著提升性能:
import functools
from typing import Callable, Any, Dict, Tuple
def cache(maxsize: int = 128):
"""简单的 LRU 缓存装饰器"""
def decorator(func: Callable) -> Callable:
cache_store: Dict[Tuple, Any] = {}
access_order = []
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 创建缓存键
key = (args, tuple(sorted(kwargs.items())))
if key in cache_store:
print(f"[缓存命中] {func.__name__}")
# 更新访问顺序
access_order.remove(key)
access_order.append(key)
return cache_store[key]
print(f"[缓存未命中] 计算 {func.__name__}")
result = func(*args, **kwargs)
# 存储结果
if len(cache_store) >= maxsize:
# 移除最久未使用的
oldest_key = access_order.pop(0)
del cache_store[oldest_key]
cache_store[key] = result
access_order.append(key)
return result
wrapper.cache_clear = lambda: cache_store.clear()
wrapper.cache_info = lambda: f"缓存大小:{len(cache_store)}/{maxsize}"
return wrapper
return decorator
# 使用示例
@cache(maxsize=5)
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 55
print(fibonacci(10)) # 缓存命中
print(fibonacci(8)) # 缓存命中
这个实现展示了 LRU(最近最少使用)缓存策略的核心思想。当缓存满时,移除最久未访问的元素。
技巧四:重试装饰器
网络请求或文件操作可能会临时失败,自动重试可以提高程序的健壮性:
import functools
import time
import random
from typing import Callable, Any, Type, Tuple
def retry(
max_attempts: int = 3,
delay: float = 1.0,
backoff: float = 2.0,
exceptions: Tuple[Type[Exception], ...] = (Exception,)
):
"""失败后自动重试的装饰器"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Any:
current_delay = delay
last_exception = None
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
last_exception = e
if attempt < max_attempts:
print(f"尝试 {attempt}/{max_attempts} 失败:{e}")
print(f"等待 {current_delay:.1f} 秒后重试...")
time.sleep(current_delay)
current_delay *= backoff
else:
print(f"所有 {max_attempts} 次尝试均失败")
raise last_exception
return wrapper
return decorator
# 使用示例
@retry(max_attempts=3, delay=0.5, backoff=2.0)
def unstable_operation():
if random.random() < 0.7: # 70% 概率失败
raise ConnectionError("网络连接失败")
return "操作成功"
try:
result = unstable_operation()
print(result)
except Exception as e:
print(f"最终失败:{e}")
指数退避(exponential backoff)是重试策略中的经典模式,每次重试的等待时间成倍增长,避免对服务造成过大压力。
技巧五:权限验证装饰器
在 Web 应用或 CLI 工具中,经常需要验证用户权限。装饰器可以优雅地实现这一功能:
import functools
from typing import Callable, List, Any
from enum import Enum
class Role(Enum):
GUEST = 0
USER = 1
ADMIN = 2
class User:
def __init__(self, name: str, role: Role):
self.name = name
self.role = role
# 全局当前用户(实际项目中应从 session 获取)
_current_user: User = None
def set_current_user(user: User):
global _current_user
_current_user = user
def require_role(*allowed_roles: Role):
"""验证用户角色的装饰器"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Any:
if _current_user is None:
raise PermissionError("未登录")
if _current_user.role not in allowed_roles:
role_names = ", ".join(r.name for r in allowed_roles)
raise PermissionError(
f"权限不足:需要 [{role_names}],当前为 {_current_user.role.name}"
)
return func(*args, **kwargs)
return wrapper
return decorator
# 使用示例
@require_role(Role.ADMIN)
def delete_all_users():
return "已删除所有用户"
@require_role(Role.USER, Role.ADMIN)
def view_profile():
return f"当前用户:{_current_user.name}"
# 测试
set_current_user(User("张三", Role.USER))
print(view_profile()) # 成功
# delete_all_users() # 抛出 PermissionError
这种模式在 Flask、Django 等框架中非常常见。通过装饰器,权限逻辑与业务逻辑完全分离,代码更加清晰。
总结
装饰器是 Python 中最优雅的特性之一。掌握这 5 个技巧后,你可以:
- 减少重复代码,提高可维护性
- 将横切关注点(日志、权限、缓存等)与业务逻辑分离
- 编写更加 Pythonic 的代码
记住,好的装饰器应该:1)使用 functools.wraps 保留元信息;2)保持单一职责;3)有清晰的命名和文档。
希望这些技巧能帮助你在项目中写出更优雅的代码!如果你有其他的装饰器使用场景,欢迎在评论区分享。
