Python 装饰器:从基础到高级
什么是装饰器?
装饰器(Decorator)是 Python 中最优雅的特性之一。简单来说,装饰器是一个接受函数作为参数并返回新函数的可调用对象——它让你在不修改原函数代码的情况下,为函数添加额外的功能。
def my_decorator(func):
def wrapper():
print("函数执行前...")
func()
print("函数执行后...")
return wrapper
@my_decorator
def say_hello():
print("Hello, World!")
say_hello()
# 输出:
# 函数执行前...
# Hello, World!
# 函数执行后...
@my_decorator 本质上等价于 say_hello = my_decorator(say_hello)。装饰器在 Python 中是一等语法糖,但它的强大远超于此。
为什么要用装饰器?
装饰器解决了一个核心问题:横切关注点(Cross-Cutting Concerns)的分离。以下场景都是装饰器的典型应用:
- 日志记录:自动记录函数调用、参数和返回值
- 性能计时:测量函数执行时间
- 权限校验:检查用户是否有权限执行某操作
- 缓存:缓存函数返回值,避免重复计算
- 重试机制:自动重试失败的操作
- 输入验证:检查函数参数是否符合预期
如果没有装饰器,这些逻辑就会散布在业务代码中,导致难以维护。
基础装饰器模式
带参数的函数
def log_call(func):
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__},参数: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"返回: {result}")
return result
return wrapper
@log_call
def add(a, b):
return a + b
add(3, 5)
# 调用 add,参数: (3, 5), {}
# 返回: 8
关键点:使用 *args 和 **kwargs 让包装函数能接受任意参数,然后原封不动地传递给原函数。同时记得 return result——这是新手最容易犯的错误。
使用 functools.wraps 保留元信息
直接使用装饰器会导致原函数的元信息丢失:
def naive_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@naive_decorator
def greet():
"""返回问候语"""
return "Hi"
print(greet.__name__) # wrapper (不是 greet!)
print(greet.__doc__) # None (文档丢了!)
解决方案:
from functools import wraps
def better_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@better_decorator
def greet():
"""返回问候语"""
return "Hi"
print(greet.__name__) # greet ✓
print(greet.__doc__) # 返回问候语 ✓
每条黄金法则:写装饰器时始终使用 @wraps(func)。
进阶:带参数的装饰器
有时我们需要装饰器本身接受参数。例如,一个重试装饰器需要知道重试次数:
from functools import wraps
import time
def retry(max_attempts=3, delay=1):
"""重试装饰器工厂"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
print(f"第 {attempt + 1} 次失败: {e}")
if attempt < max_attempts - 1:
time.sleep(delay)
raise last_exception
return wrapper
return decorator
@retry(max_attempts=3, delay=2)
def fetch_data(url):
# 模拟可能失败的网络请求
import random
if random.random() < 0.7:
raise ConnectionError("网络超时")
return {"data": "success"}
这里有三层嵌套:retry() 返回 decorator,decorator 返回 wrapper。理解这个「俄罗斯套娃」结构是掌握装饰器的关键。
类装饰器
装饰器不限于函数,类也可以作为装饰器。使用 __call__ 方法:
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} 已被调用 {self.count} 次")
return self.func(*args, **kwargs)
@CountCalls
def process():
pass
process() # process 已被调用 1 次
process() # process 已被调用 2 次
类装饰器特别适合需要维护状态的场景,因为实例属性能自然地存储状态而不用闭包。
实战示例:构建实用的装饰器
1. 缓存装饰器(Memoization)
from functools import wraps
def memoize(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@memoize
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(100)) # 354224848179261915075 — 瞬间算出
没有缓存时,计算 fibonacci(100) 需要天文数字级别的递归调用。有了 @memoize,每个值只算一次,时间复杂度从 O(2^n) 降到 O(n)。
2. 权限校验装饰器
def require_role(role):
def decorator(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if user.get("role") != role:
raise PermissionError(f"需要 {role} 权限")
return func(user, *args, **kwargs)
return wrapper
return decorator
@require_role("admin")
def delete_user(admin_user, target_id):
return f"用户 {target_id} 已删除"
# 使用示例
admin = {"name": "Alice", "role": "admin"}
guest = {"name": "Bob", "role": "guest"}
print(delete_user(admin, 42)) # 用户 42 已删除
# delete_user(guest, 42) # PermissionError: 需要 admin 权限
3. 计时与日志装饰器
import time
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def log_execution(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
logger.info(
f"{func.__name__} 完成,耗时 {elapsed:.4f}s"
)
return result
except Exception as e:
elapsed = time.perf_counter() - start
logger.error(
f"{func.__name__} 失败,耗时 {elapsed:.4f}s,错误: {e}"
)
raise
return wrapper
@log_execution
def heavy_computation(n):
total = sum(i * i for i in range(n))
return total
heavy_computation(10_000_000)
# INFO:__main__:heavy_computation 完成,耗时 0.8234s
多个装饰器的叠加顺序
装饰器可以叠加,执行顺序是从下到上(离函数越近越先执行):
@decorator_a
@decorator_b
def my_func():
pass
等价于 my_func = decorator_a(decorator_b(my_func))。decorator_b 先包装,decorator_a 后包装。执行时则相反——就像洋葱,外层先进,内层后出。
总结
| 层级 | 结构 | 使用场景 |
|---|---|---|
| 基础装饰器 | func → wrapper |
简单日志、计时 |
| 带参数装饰器 | params → decorator → wrapper |
可配置的重试、权限 |
| 类装饰器 | __init__ + __call__ |
需要维护状态的场景 |
装饰器的精髓在于 AOP(面向切面编程) 思想:将横切关注点从业务逻辑中抽离,让代码更干净、更可测试、更可维护。
掌握了装饰器,你的 Python 代码将从「能跑就行」进化到「优雅且健壮」。动手试一下吧——把项目中重复的日志、计时、权限检查逻辑提炼成装饰器,你会立刻感受到代码质量的飞跃。