Python 装饰器从入门到实战:5 个实用场景详解
装饰器(Decorator)是 Python 中一种优雅的设计模式,它允许我们在不修改原函数代码的前提下,动态地给函数添加功能。想象一下,你有一个已经写好的函数,现在想给它添加日志记录、性能监控或权限检查等功能,装饰器就是为此而生的工具。
理解装饰器的关键在于明白它本质上是一个"函数的包装器"。它接收一个函数作为输入,返回一个新的函数,这个新函数在调用原函数前后可以执行额外的逻辑。这种设计遵循了开放封闭原则——对扩展开放,对修改封闭。
让我们从一个最简单的装饰器开始。假设我们想给多个函数添加执行时间统计功能,传统做法是在每个函数内部添加计时代码,但这会导致代码重复。使用装饰器,我们可以将计时逻辑封装一次,然后应用到任意函数上:
python
import time
from functools import wraps
def timer_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} 执行耗时:{end - start:.4f}秒")
return result
return wrapper
@timer_decorator
def calculate_sum(n):
return sum(i * i for i in range(n))
@timer_decorator
def slow_operation():
time.sleep(1)
return "完成"
calculate_sum(1000000)
slow_operation()
这个示例展示了装饰器的基本结构:外层函数接收被装饰的函数,内层 wrapper 函数负责执行额外逻辑。使用@wraps 装饰器可以保留原函数的元信息,如函数名、文档字符串等,这在调试时非常重要。
实际开发中,我们经常需要带参数的装饰器。比如实现一个可配置的重试机制,当函数执行失败时自动重试指定次数:
python
import random
from functools import wraps
def retry(max_attempts=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
if attempts >= max_attempts:
raise
print(f"第{attempts}次重试,等待{delay}秒...")
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=3, delay=0.5)
def unstable_api():
if random.random() < 0.7:
raise ConnectionError("网络波动")
return "API 调用成功"
print(unstable_api())
带参数的装饰器需要三层嵌套:最外层接收装饰器参数,中间层接收被装饰函数,最内层是实际执行的 wrapper。这种模式在实现可配置的行为时非常有用。
缓存是另一个装饰器的经典应用场景。对于计算密集型或 IO 密集型函数,我们可以缓存之前的计算结果,避免重复执行:
python
from functools import wraps
def simple_cache(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args in cache:
print(f"从缓存命中:{args}")
return cache[args]
result = func(*args)
cache[args] = result
print(f"缓存新结果:{args}")
return result
return wrapper
@simple_cache
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10))
print(fibonacci(10)) # 直接从缓存读取
这个简单的缓存装饰器展示了闭包的力量——cache 字典在 wrapper 函数外部定义,但在内部被引用,因此可以在多次函数调用之间保持状态。实际项目中,Python 标准库的 functools.lru_cache 提供了更完善的实现。
权限验证是 Web 开发中的常见需求。使用装饰器,我们可以将权限检查逻辑与业务逻辑分离,使代码更加清晰:
python
from functools import wraps
class User:
def __init__(self, name, role):
self.name = name
self.role = role
current_user = None
def require_role(required_role):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if current_user is None:
raise PermissionError("用户未登录")
if current_user.role != required_role:
raise PermissionError(f"需要{required_role}权限")
return func(*args, **kwargs)
return wrapper
return decorator
@require_role("admin")
def delete_user(user_id):
return f"删除用户 {user_id}"
@require_role("user")
def view_profile():
return f"查看 {current_user.name} 的个人资料"
current_user = User("张三", "admin")
print(delete_user(123))
最后一个实用场景是日志记录。在生产环境中,记录函数的输入输出对于排查问题至关重要:
python
import logging
from functools import wraps
from datetime import datetime
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
logger.info(f"[{timestamp}] 调用 {func.__name__}")
logger.info(f"参数:args={args}, kwargs={kwargs}")
try:
result = func(*args, **kwargs)
logger.info(f"返回:{result}")
return result
except Exception as e:
logger.error(f"异常:{e}")
raise
return wrapper
@log_calls
def divide(a, b):
return a / b
divide(10, 2)
通过以上 5 个场景,我们可以看到装饰器的强大之处:它将横切关注点(如日志、缓存、权限)与核心业务逻辑分离,使代码更加模块化、可维护。掌握装饰器后,你可以编写出更加优雅和 Pythonic 的代码。
装饰器的学习曲线可能有些陡峭,但一旦理解其本质——函数的高阶应用和闭包的结合——就会发现它是 Python 中最优雅的特性之一。建议从简单的装饰器开始练习,逐步过渡到带参数和类装饰器,最终能够灵活运用这一强大工具。
