Python 装饰器完全指南:从入门到实战
Python 装饰器完全指南:从入门到实战
引言
在 Python 编程的世界里,装饰器(Decorator)无疑是最优雅也最令人困惑的特性之一。许多初学者第一次见到 INLINE_CODE_0 符号时都会感到迷茫,而经验丰富的开发者则将其视为提升代码质量的利器。
装饰器本质上是一个函数,它接受另一个函数作为参数,并返回一个新的函数。这种"函数包装函数"的模式,让我们能够在不修改原函数代码的前提下,为其添加额外的功能。本文将带你深入理解装饰器的工作原理,并通过大量实战案例展示其强大之处。
一、装饰器的基础概念
1.1 什么是装饰器?
装饰器是一种设计模式,它允许程序员在不改变原函数代码的情况下,动态地为函数添加功能。想象一下,你有一个精美的礼物盒(原函数),装饰器就像是包装纸和丝带,你可以给礼物盒加上漂亮的包装,而不需要打开或改变盒子里的东西。
1.2 为什么需要装饰器?
在实际开发中,我们经常遇到这样的需求:
- 在函数执行前后添加日志记录
- 验证用户权限
- 测量函数执行时间
- 实现缓存机制
- 处理异常和重试逻辑
如果没有装饰器,我们可能需要在每个函数中重复编写相同的代码。装饰器让我们能够将这些通用逻辑抽离出来,实现代码的复用和解耦。
二、理解装饰器的工作原理
2.1 函数是一等公民
要理解装饰器,首先要明白在 Python 中,函数是"一等公民"(First-Class Citizen)。这意味着:
- 函数可以赋值给变量
- 函数可以作为参数传递给其他函数
- 函数可以作为另一个函数的返回值
# 函数可以赋值给变量
def greet():
return "Hello!"
say_hello = greet
print(say_hello()) # 输出:Hello!
# 函数可以作为参数
def execute(func):
return func()
print(execute(greet)) # 输出:Hello!
# 函数可以作为返回值
def create_greeter():
def greet():
return "Hello!"
return greet
greeter = create_greeter()
print(greeter()) # 输出:Hello!
2.2 装饰器的基本结构
一个最简单的装饰器由三部分组成:
- 外层函数(装饰器本身)
- 内层函数(包装函数)
- 返回内层函数
def simple_decorator(func):
"""最简单的装饰器"""
def wrapper():
print("函数执行前")
func()
print("函数执行后")
return wrapper
@simple_decorator
def say_hello():
print("Hello!")
say_hello()
# 输出:
# 函数执行前
# Hello!
# 函数执行后
INLINE_CODE_1 语法糖等价于 INLINE_CODE_2。
三、装饰器的进阶用法
3.1 带参数的装饰器
实际应用中,我们常常需要装饰器能够接受参数。这就需要三层嵌套:
def repeat(times):
"""重复执行指定次数的装饰器"""
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(times):
print(f"第 {i + 1} 次执行:")
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("World")
3.2 保留原函数信息
使用装饰器后,原函数的 INLINE_CODE_3 和 INLINE_CODE_4 会被包装函数覆盖。我们可以使用 INLINE_CODE_5 来解决这个问题:
from functools import wraps
def logging_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"调用函数:{func.__name__}")
return func(*args, **kwargs)
return wrapper
@logging_decorator
def add(a, b):
"""两数相加"""
return a + b
print(add.__name__) # 输出:add(而不是 wrapper)
print(add.__doc__) # 输出:两数相加
3.3 类装饰器
除了函数装饰器,Python 还支持类装饰器:
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)
@CountCalls
def say_hello():
print("Hello!")
say_hello()
say_hello()
print(f"总共调用 {say_hello.count} 次")
四、实战应用场景
4.1 性能监控装饰器
在开发中,我们经常需要知道某个函数的执行时间,以便进行性能优化:
import time
from functools import wraps
def timing_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 执行时间:{end - start:.4f} 秒")
return result
return wrapper
@timing_decorator
def slow_function():
time.sleep(1)
return "完成"
slow_function()
4.2 缓存装饰器
对于计算密集型且结果可预测的函数,缓存可以大幅提升性能:
from functools import wraps
def cache_decorator(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args in cache:
print(f"从缓存获取结果")
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@cache_decorator
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 第一次计算
print(fibonacci(10)) # 从缓存获取
4.3 权限验证装饰器
在 Web 开发中,权限验证是常见需求:
from functools import wraps
def require_permission(permission):
def decorator(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if permission not in user.get('permissions', []):
raise PermissionError(f"用户缺少 {permission} 权限")
return func(user, *args, **kwargs)
return wrapper
return decorator
@require_permission('admin')
def delete_user(user, target_user):
return f"删除用户 {target_user}"
# 测试
admin_user = {'name': 'admin', 'permissions': ['admin', 'edit']}
normal_user = {'name': 'user', 'permissions': ['read']}
print(delete_user(admin_user, 'test')) # 成功
# delete_user(normal_user, 'test') # 抛出权限错误
4.4 重试装饰器
处理网络请求等不稳定操作时,重试机制非常有用:
import time
from functools import wraps
def retry(max_attempts=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
print(f"第 {attempt + 1} 次失败,{delay}秒后重试:{e}")
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=3, delay=2)
def unstable_operation():
import random
if random.random() < 0.7:
raise ConnectionError("网络连接失败")
return "操作成功"
4.5 日志记录装饰器
统一的日志记录对于系统维护至关重要:
import logging
from functools import wraps
logging.basicConfig(level=logging.INFO)
def log_decorator(logger=None):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
log_msg = f"调用 {func.__name__}, 参数:args={args}, kwargs={kwargs}"
if logger:
logger.info(log_msg)
else:
logging.info(log_msg)
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
return decorator
@log_decorator()
def divide(a, b):
return a / b
divide(10, 2)
五、装饰器栈与执行顺序
多个装饰器可以堆叠使用,执行顺序是从下往上(靠近函数的先执行),返回时从上往下:
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 test():
print("函数执行")
return "结果"
test()
# 输出顺序:
# B: 进入
# 函数执行
# B: 离开
# A: 进入
# A: 离开
六、最佳实践与注意事项
6.1 使用 functools.wraps
始终使用 INLINE_CODE_6 保留原函数的元数据,这对于调试和文档生成非常重要。
6.2 处理可变参数
确保装饰器能够正确处理 INLINE_CODE_7 和 INLINE_CODE_8,以适配各种函数签名。
6.3 避免副作用
装饰器应该尽可能保持"纯净",避免产生意外的副作用。
6.4 性能考虑
装饰器会增加函数调用的开销,对于性能敏感的代码路径,需要权衡使用。
结语
装饰器是 Python 中最强大的特性之一,它让我们能够以优雅的方式实现代码复用和功能扩展。从简单的日志记录到复杂的权限控制,装饰器的应用场景无处不在。
掌握装饰器的关键在于理解其本质——函数包装函数。一旦理解了这一点,你就能够灵活运用装饰器来解决各种实际问题。希望本文能够帮助你深入理解装饰器,并在实际项目中发挥其最大价值。
记住,好的代码不仅是能运行的代码,更是易于理解、易于维护的代码。装饰器正是帮助我们写出这样代码的有力工具。