别再写重复代码了!5个Python装饰器让你的代码优雅10倍
前言
你有没有发现,自己写的代码里总有一些"套路"——计时、日志、缓存、权限检查……这些逻辑到处重复,代码越来越臃肿。
今天给大家分享一个 Python 里被严重低估的神器:装饰器(Decorator)。学会它,你会发现很多重复代码都可以一行搞定。
什么是装饰器?
简单说,装饰器就是一个"函数的包装器"——它不改变函数本身的功能,但在函数执行前后加了一些额外行为。
用生活中的例子:手机壳不改变手机的功能,但给它加了防摔和美观的"装饰"。装饰器对函数做的事情,就是手机壳对手机做的事情。
1. 计时装饰器:精准测量函数耗时
性能优化第一步,是知道哪里慢。
import time
import functools
def timer(func):
"""测量函数执行时间的装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"[{func.__name__}] 耗时: {elapsed:.4f}秒")
return result
return wrapper
# 使用示例
@timer
def process_data(data):
"""模拟数据处理"""
time.sleep(1.2)
return [x * 2 for x in data]
result = process_data([1, 2, 3, 4, 5])
# 输出: [process_data] 耗时: 1.2001秒
**小贴士**:用 `time.perf_counter()` 而不是 `time.time()`,前者精度更高,不受系统时间调整影响。
2. 重试装饰器:网络请求不再怕断连
写爬虫或调 API 的时候,网络不稳定很常见。与其每个函数里都写 try-except,不如一个装饰器搞定:
import functools
import time
import random
def retry(max_attempts=3, delay=1, backoff=2, exceptions=(Exception,)):
"""
自动重试装饰器
- max_attempts: 最大重试次数
- delay: 初始等待时间(秒)
- backoff: 每次重试的等待时间倍数
- exceptions: 触发重试的异常类型
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
current_delay = delay
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
if attempt == max_attempts:
print(f"[{func.__name__}] 重试{max_attempts}次后仍失败: {e}")
raise
wait = current_delay + random.uniform(0, 0.5)
print(f"[{func.__name__}] 第{attempt}次失败, {wait:.1f}秒后重试...")
time.sleep(wait)
current_delay *= backoff
return wrapper
return decorator
# 使用示例
@retry(max_attempts=3, delay=1, backoff=2, exceptions=(ConnectionError, TimeoutError))
def fetch_api(url):
"""模拟网络请求"""
if random.random() < 0.5:
raise ConnectionError("网络连接失败")
return {"status": "ok", "data": "hello"}
# 多试几次,观察重试行为
for i in range(5):
fetch_api("https://api.example.com/data")
这里用了指数退避策略,每次等待时间翻倍,是业界公认的最佳实践。
3. 缓存装饰器:重复计算说拜拜
有些函数计算很耗时,但同样的参数结果不会变。用缓存可以极大提升性能:
import functools
def memoize(func):
"""简易缓存装饰器(适用于不可变参数)"""
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
# 添加手动清除缓存的方法
wrapper.cache_clear = lambda: cache.clear()
wrapper.cache_info = lambda: f"缓存条目数: {len(cache)}"
return wrapper
# 使用示例
@memoize
def fibonacci(n):
"""计算斐波那契数列(递归版,无缓存会非常慢)"""
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# 第一次计算,需要递归
print(fibonacci(35)) # 输出: 9227465
print(fibonacci.cache_info()) # 输出: 缓存条目数: 36
# 第二次直接从缓存取,瞬间完成
print(fibonacci(35)) # 瞬间输出: 9227465
**进阶**:Python 标准库自带了 `@functools.lru_cache`,支持 LRU(最近最少使用)淘汰策略,生产环境推荐用这个。
4. 日志装饰器:函数调用一目了然
调试的时候,想知道哪个函数被调用了、传了什么参数、返回了什么:
import functools
import logging
# 配置日志格式
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%H:%M:%S"
)
logger = logging.getLogger(__name__)
def log_calls(func):
"""记录函数调用信息的装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
args_str = ", ".join(repr(a) for a in args)
kwargs_str = ", ".join(f"{k}={v!r}" for k, v in kwargs.items())
all_args = ", ".join(filter(None, [args_str, kwargs_str]))
logger.info(f"调用 {func.__name__}({all_args})")
result = func(*args, **kwargs)
logger.info(f"{func.__name__} 返回 {result!r}")
return result
return wrapper
# 使用示例
@log_calls
def add_user(name, age, role="普通用户"):
return {"name": name, "age": age, "role": role}
@log_calls
def calculate_discount(price, discount):
return round(price * discount, 2)
add_user("张三", 25)
add_user("李四", 30, role="管理员")
calculate_discount(199.00, 0.8)
输出类似这样:
08:15:30 [INFO] 调用 add_user('张三', 25)
08:15:30 [INFO] add_user 返回 {'name': '张三', 'age': 25, 'role': '普通用户'}
08:15:31 [INFO] 调用 add_user('李四', 30, role='管理员')
08:15:31 [INFO] add_user 返回 {'name': '李四', 'age': 30, 'role': '管理员'}
5. 权限检查装饰器:一行代码保护敏感操作
在 Web 开发中,权限检查是最常见的横切关注点:
import functools
# 模拟用户角色
USERS = {
"admin": {"name": "管理员", "role": "admin"},
"editor": {"name": "编辑", "role": "editor"},
"guest": {"name": "访客", "role": "guest"},
}
def require_role(*allowed_roles):
"""权限检查装饰器工厂"""
def decorator(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if user.get("role") not in allowed_roles:
raise PermissionError(
f"权限不足: 需要 {allowed_roles},当前角色 '{user.get('role')}'"
)
return func(user, *args, **kwargs)
return wrapper
return decorator
# 使用示例
@require_role("admin")
def delete_all_users(user):
return f"{user['name']} 执行了清空用户操作"
@require_role("admin", "editor")
def publish_article(user, title):
return f"{user['name']} 发布了文章: {title}"
# 正常执行
print(publish_article(USERS["editor"], "Python装饰器入门"))
# 输出: 编辑 发布了文章: Python装饰器入门
# 权限不足
try:
delete_all_users(USERS["guest"])
except PermissionError as e:
print(e) # 输出: 权限不足: 需要 ('admin',),当前角色 'guest'
总结:装饰器的核心模式
以上5个装饰器其实都遵循同一个模式:
import functools
def my_decorator(func):
@functools.wraps(func) # 保留原函数信息(重要!)
def wrapper(*args, **kwargs):
# 前置逻辑
result = func(*args, **kwargs) # 调用原函数
# 后置逻辑
return result
return wrapper
记住这几点:
1. 永远加 @functools.wraps,否则你的函数会丢失 __name__ 和 __doc__
2. 装饰器本质上就是高阶函数——接受函数作为参数,返回新函数
3. 带参数的装饰器需要多一层嵌套(装饰器工厂模式)
4. Python 3.10+ 可以用 @timer 或 @timer() 两种写法,后者的定义要多一层
装饰器是 Python 最优雅的设计之一。一旦你习惯用它来处理横切关注点,回头再看到处 copy-paste 的代码,你会庆幸自己学会了这个技能。
你在项目中用过装饰器吗?遇到了什么坑? 欢迎在评论区聊聊你的经验,点赞收藏不迷路,后续会分享更多 Python 实战技巧!
更多推荐


所有评论(0)