什么是Python装饰器?
装饰器(Decorator)是Python中一种强大的语法特性,它允许你在不修改原有函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个高阶函数,它接收一个函数作为参数,并返回一个新的函数。
装饰器的核心概念
装饰器的工作原理基于Python的函数是一等公民(First-class objects)这一特性。这意味着函数可以像其他对象一样被传递、赋值和返回。
# 一个简单的装饰器示例
def my_decorator(func):
def wrapper():
print("函数执行前")
func()
print("函数执行后")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
# 调用被装饰的函数
say_hello()
输出结果:
函数执行前
Hello!
函数执行后
装饰器的基本语法
1. 使用@符号的装饰器
Python提供了@语法糖来简化装饰器的使用:
def timer_decorator(func):
import time
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
@timer_decorator
def calculate_sum(n):
return sum(range(n))
# 使用示例
result = calculate_sum(1000000)
print(f"计算结果: {result}")
2. 带参数的装饰器
有时候我们需要让装饰器本身接受参数:
def repeat(times):
def decorator(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("Alice")
输出:
你好, Alice!
你好, Alice!
你好, Alice!
装饰器的高级应用
1. 类装饰器
装饰器不仅可以是函数,也可以是类:
class CountCalls:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"函数 {self.func.__name__} 被调用了 {self.num_calls} 次")
return self.func(*args, **kwargs)
@CountCalls
def say_whee():
print("Whee!")
say_whee()
say_whee()
2. 多个装饰器的组合
多个装饰器可以堆叠使用,执行顺序是从下到上:
def make_bold(func):
def wrapper():
return f"<b>{func()}</b>"
return wrapper
def make_italic(func):
def wrapper():
return f"<i>{func()}</i>"
return wrapper
@make_bold
@make_italic
def greet():
return "Hello World"
print(greet())
# 输出: <b><i>Hello World</i></b>
实际应用场景
1. 日志记录装饰器
import logging
from functools import wraps
def log_execution(func):
@wraps(func) # 保留原函数的元数据
def wrapper(*args, **kwargs):
logging.info(f"开始执行: {func.__name__}")
try:
result = func(*args, **kwargs)
logging.info(f"执行成功: {func.__name__}")
return result
except Exception as e:
logging.error(f"执行失败: {func.__name__}, 错误: {e}")
raise
return wrapper
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
@log_execution
def divide(a, b):
return a / b
# 测试
try:
print(divide(10, 2))
print(divide(10, 0))
except:
pass
2. 缓存装饰器(记忆化)
from functools import lru_cache
import time
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
# 测试性能
start = time.time()
result = fibonacci(30)
end = time.time()
print(f"结果: {result}, 耗时: {end - start:.4f}秒")
# 再次调用,速度会快很多
start = time.time()
result = fibonacci(30)
end = time.time()
print(f"再次调用结果: {result}, 耗时: {end - start:.4f}秒")
3. 权限验证装饰器
class User:
def __init__(self, username, role):
self.username = username
self.role = role
def require_role(required_role):
def decorator(func):
def wrapper(user, *args, **kwargs):
if user.role != required_role:
raise PermissionError(f"需要 {required_role} 权限")
return func(user, *args, **kwargs)
return wrapper
return decorator
@require_role("admin")
def delete_user(user, target_username):
print(f"用户 {user.username} 删除了 {target_username}")
# 测试
admin = User("alice", "admin")
guest = User("bob", "guest")
delete_user(admin, "charlie") # 成功
try:
delete_user(guest, "charlie") # 失败
except PermissionError as e:
print(f"错误: {e}")
装饰器的最佳实践
1. 使用functools.wraps
始终使用@wraps来保留原函数的元数据:
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""包装函数的文档字符串"""
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""原始函数的文档字符串"""
pass
print(example.__name__) # 输出: example (而不是wrapper)
print(example.__doc__) # 输出: 原始函数的文档字符串
2. 装饰器的参数验证
def validate_input(*validations):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for i, (arg, validation) in enumerate(zip(args, validations)):
if not validation(arg):
raise ValueError(f"参数 {i} 验证失败: {arg}")
return func(*args, **kwargs)
return wrapper
return decorator
# 使用示例
@validate_input(lambda x: x > 0, lambda y: y > 0)
def calculate_area(width, height):
return width * height
print(calculate_area(5, 10)) # 成功
try:
print(calculate_area(-5, 10)) # 失败
except ValueError as e:
print(f"错误: {e}")
装饰器的调试技巧
1. 检查装饰器是否正确应用
def debug_decorator(func):
print(f"装饰器应用于函数: {func.__name__}")
def wrapper(*args, **kwargs):
print(f"调用函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@debug_decorator
def test_function():
pass
# 输出: 装饰器应用于函数: test_function
2. 使用inspect模块检查装饰器
import inspect
def my_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def example():
pass
# 检查包装后的函数
print(inspect.unwrap(example)) # 获取原始函数
常见陷阱和解决方案
1. 装饰器不保留原函数信息
问题:
def bad_decorator(func):
def wrapper():
return func()
return wrapper
@bad_decorator
def my_func():
"""文档字符串"""
pass
print(my_func.__name__) # 输出: wrapper (不是my_func)
解决方案:
from functools import wraps
def good_decorator(func):
@wraps(func)
def wrapper():
return func()
return wrapper
2. 装饰器参数传递错误
# 错误示例
def decorator_with_args(arg1, arg2=None):
def decorator(func):
def wrapper():
print(f"arg1={arg1}, arg2={arg2}")
return func()
return wrapper
return decorator
@decorator_with_args("hello") # 正确
@decorator_with_args("hello", "world") # 正确
def my_func():
pass
总结
装饰器是Python中非常强大和灵活的工具,掌握它们可以让你的代码更加优雅和可维护。记住以下要点:
- 始终使用
@wraps保留原函数元数据 - 理解执行顺序:多个装饰器从下到上执行
- 合理使用参数:带参数的装饰器需要三层嵌套
- 保持简单:装饰器应该专注于单一职责
- 充分测试:确保装饰器在各种情况下都能正常工作
通过本文的详细示例和解释,你现在应该对Python装饰器有了深入的理解,可以在实际项目中灵活运用它们来提升代码质量。
