Python 装饰器完全指南:从入门到实战应用
Python 装饰器完全指南:从入门到实战应用
引言
装饰器(Decorator)是 Python 中最强大也最令人困惑的特性之一。它允许你在不修改原函数代码的情况下,动态地增强或修改函数的行为。本文将带你从基础概念到高级应用,全面掌握 Python 装饰器的使用技巧。
很多初学者第一次看到 INLINE_CODE_0 语法时都会感到困惑:这是什么魔法?为什么函数上面要加个 INLINE_CODE_1 符号?实际上,装饰器的本质非常简单——它就是一个接收函数作为参数并返回新函数的高阶函数。
一、装饰器的基础概念
1.1 什么是装饰器?
装饰器本质上是一个 callable 对象(函数或类),它接受一个函数作为输入,并返回一个新的函数。这个新模式通常会在原函数的基础上添加一些额外的功能。
用数学语言来说,如果 INLINE_CODE_2 是原函数,INLINE_CODE_3 是装饰器,那么:
decorated_f = decorator(f)
而 INLINE_CODE_4 语法糖等价于:
@decorator
def f():
pass
# 等价于
def f():
pass
f = decorator(f)
1.2 第一个装饰器示例
让我们从一个最简单的例子开始:
def simple_decorator(func):
"""最简单的装饰器"""
def wrapper(*args, **kwargs):
print("函数执行前")
result = func(*args, **kwargs)
print("函数执行后")
return result
return wrapper
@simple_decorator
def greet(name):
print(f"你好,{name}!")
return f"问候完成"
# 调用
result = greet("张三")
# 输出:
# 函数执行前
# 你好,张三!
# 函数执行后
# result = "问候完成"
这个例子展示了装饰器的基本结构:
- 外层函数接收原函数作为参数
- 内层 wrapper 函数接收原函数的参数
- 在调用原函数前后添加额外逻辑
- 返回 wrapper 函数
二、装饰器的核心原理
2.1 函数是一等公民
Python 中函数是一等公民(first-class citizen),这意味着:
- 函数可以作为参数传递给其他函数
- 函数可以作为返回值从函数中返回
- 函数可以赋值给变量
装饰器正是利用了这一特性。
2.2 闭包的作用
装饰器中的 wrapper 函数使用了闭包(closure)来访问外层函数的变量。让我们深入理解:
def counter_decorator(func):
count = 0 # 这个变量会被闭包捕获
def wrapper(*args, **kwargs):
nonlocal count
count += 1
print(f"{func.__name__} 已被调用 {count} 次")
return func(*args, **kwargs)
return wrapper
@counter_decorator
def say_hello():
print("Hello!")
say_hello() # 已被调用 1 次
say_hello() # 已被调用 2 次
say_hello() # 已被调用 3 次
INLINE_CODE_5 关键字允许我们在内层函数中修改外层函数的变量。
三、保留函数元信息
3.1 functools.wraps 的重要性
使用装饰器后,原函数的元信息(如 INLINE_CODE_6、INLINE_CODE_7)会丢失:
def my_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def original_function():
"""这是原文档字符串"""
pass
print(original_function.__name__) # 输出:wrapper(错误!)
print(original_function.__doc__) # 输出:None(错误!)
使用 INLINE_CODE_8 可以保留原函数的元信息:
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def original_function():
"""这是原文档字符串"""
pass
print(original_function.__name__) # 输出:original_function ✓
print(original_function.__doc__) # 输出:这是原文档字符串 ✓
最佳实践:始终使用 INLINE_CODE_9 装饰你的 wrapper 函数!
四、带参数的装饰器
4.1 装饰器工厂模式
有时我们需要给装饰器本身传递参数,这需要三层嵌套:
def repeat(times):
"""带参数的装饰器工厂"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"你好,{name}!")
greet("李四")
# 输出三次:你好,李四!
结构解析:
- 最外层 INLINE_CODE_10 接收装饰器参数
- 中间层 INLINE_CODE_11 接收被装饰的函数
- 最内层 INLINE_CODE_12 接收函数调用参数
4.2 实际应用:重试机制
import time
from functools import wraps
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}/{max_attempts} 失败:{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 "数据获取成功"
五、类装饰器
5.1 用类实现装饰器
装饰器也可以是类,通过实现 INLINE_CODE_13 方法:
class CountCalls:
"""统计函数调用次数的类装饰器"""
def __init__(self, func):
wraps(func)(self)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"调用次数:{self.count}")
return self.func(*args, **kwargs)
def reset_count(self):
self.count = 0
@CountCalls
def say_hello():
print("Hello!")
say_hello() # 调用次数:1
say_hello() # 调用次数:2
print(f"总调用:{say_hello.count}次") # 总调用:2 次
5.2 带参数的类装饰器
class RateLimiter:
"""速率限制装饰器"""
def __init__(self, max_calls, period):
self.max_calls = max_calls
self.period = period
self.calls = []
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
now = time.time()
# 移除超出时间窗口的调用记录
self.calls = [t for t in self.calls if now - t < self.period]
if len(self.calls) >= self.max_calls:
raise Exception(f"速率限制:{self.max_calls} 次/{self.period}秒")
self.calls.append(now)
return func(*args, **kwargs)
return wrapper
@RateLimiter(max_calls=5, period=10)
def api_call():
return "API 响应"
六、实际应用场景
6.1 日志记录
def log_calls(func):
"""记录函数调用的装饰器"""
@wraps(func)
def wrapper(*args, **kwargs):
import logging
logging.info(f"调用 {func.__name__}, 参数:args={args}, kwargs={kwargs}")
try:
result = func(*args, **kwargs)
logging.info(f"{func.__name__} 返回:{result}")
return result
except Exception as e:
logging.error(f"{func.__name__} 抛出异常:{e}")
raise
return wrapper
6.2 性能分析
def timing(func):
"""测量函数执行时间的装饰器"""
@wraps(func)
def wrapper(*args, **kwargs):
import time
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} 执行时间:{end - start:.4f} 秒")
return result
return wrapper
@timing
def slow_function():
time.sleep(1)
return "完成"
6.3 权限验证
def require_auth(func):
"""权限验证装饰器"""
@wraps(func)
def wrapper(user, *args, **kwargs):
if not user.is_authenticated:
raise PermissionError("用户未认证")
if not user.has_permission(func.__name__):
raise PermissionError("用户无权限")
return func(user, *args, **kwargs)
return wrapper
@require_auth
def delete_resource(user, resource_id):
# 只有认证且有权限的用户才能执行
pass
6.4 缓存(记忆化)
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)) # 瞬间完成,无需重复计算
6.5 事务管理
def transaction(func):
"""数据库事务装饰器"""
@wraps(func)
def wrapper(*args, **kwargs):
db.begin_transaction()
try:
result = func(*args, **kwargs)
db.commit()
return result
except Exception as e:
db.rollback()
raise
return wrapper
@transaction
def transfer_money(from_account, to_account, amount):
# 要么全部成功,要么全部回滚
pass
七、装饰器栈(多个装饰器)
一个函数可以应用多个装饰器,从下往上执行:
def decorator_a(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("装饰器 A - 前")
result = func(*args, **kwargs)
print("装饰器 A - 后")
return result
return wrapper
def decorator_b(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("装饰器 B - 前")
result = func(*args, **kwargs)
print("装饰器 B - 后")
return result
return wrapper
@decorator_a
@decorator_b
def hello():
print("Hello!")
hello()
# 输出顺序:
# 装饰器 A - 前
# 装饰器 B - 前
# Hello!
# 装饰器 B - 后
# 装饰器 A - 后
八、常见陷阱与最佳实践
8.1 陷阱:忘记使用 @wraps
# 错误示范
def bad_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
# 正确示范
def good_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
8.2 陷阱:装饰器修改可变参数
def dangerous_decorator(func):
@wraps(func)
def wrapper(items, *args, **kwargs):
items.append("modified") # 修改了传入的可变对象!
return func(items, *args, **kwargs)
return wrapper
解决方案:复制可变参数后再修改
8.3 最佳实践清单
- ✅ 始终使用 INLINE_CODE_14 保留元信息
- ✅ 使用 INLINE_CODE_15 保证通用性
- ✅ 装饰器函数命名清晰,说明用途
- ✅ 避免在装饰器中修改可变参数
- ✅ 复杂逻辑考虑用类装饰器
- ✅ 为装饰器编写文档字符串
- ✅ 单元测试覆盖装饰器边界情况
结语
装饰器是 Python 中最优雅的特性之一,它让代码更加模块化、可复用和易维护。从简单的日志记录到复杂的权限控制,装饰器的应用场景无处不在。
掌握装饰器的关键在于理解:
- 函数是一等公民,可以作为参数和返回值
- 闭包让内层函数可以访问外层变量
- INLINE_CODE_16 保留原函数元信息
- 装饰器工厂模式支持参数化
- 类装饰器适合需要状态的场景
希望本教程能帮助你真正理解并熟练运用 Python 装饰器。记住,最好的学习方式是实践——尝试为你的项目编写一些实用的装饰器吧!
参考资料:
- Python 官方文档:Decorators
- 《Fluent Python》第 7 章
- Real Python: Decorators in Python