引言
Python装饰器(Decorator)是Python语言中一个强大而优雅的特性,它允许开发者在不修改原有函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个高阶函数,它接收一个函数作为参数并返回一个新的函数。这种设计模式遵循了软件工程中的开放-封闭原则,即对扩展开放,对修改封闭。
装饰器的语法使用Python的@符号,这使得代码更加简洁和可读。通过装饰器,我们可以轻松实现日志记录、性能测试、权限验证、事务处理等横切关注点(Cross-Cutting Concerns),从而保持核心业务逻辑的清晰和纯粹。
装饰器的基本概念
什么是装饰器
装饰器是一个可调用对象(callable),它接收一个函数作为参数,并返回一个新的函数。装饰器可以在不修改原函数代码的情况下,为函数添加新的功能。
# 最简单的装饰器示例
def my_decorator(func):
def wrapper():
print("在函数调用前执行")
func()
print("在函数调用后执行")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
# 调用装饰后的函数
say_hello()
输出结果:
在函数调用前执行
Hello!
在函数调用后执行
装饰器的工作原理
当Python解释器遇到@decorator语法时,它实际上执行了以下操作:
# @my_decorator 语法等价于:
def say_hello():
print("Hello!")
say_hello = my_decorator(say_hello)
这意味着装饰器在函数定义时就被调用,并将原函数替换为装饰器返回的新函数。
带参数的函数装饰
处理带参数的函数
当被装饰的函数需要参数时,我们需要在包装函数中也接受相应的参数:
def greet_decorator(func):
def wrapper(*args, **kwargs):
print("准备执行函数...")
result = func(*args, **kwargs)
print("函数执行完成!")
return result
return wrapper
@greet_decorator
def add(a, b):
return a + b
print(add(3, 5))
输出:
准备执行函数...
函数执行完成!
8
保留原函数元信息
默认情况下,装饰器会隐藏原函数的名称和文档字符串。使用functools.wraps可以解决这个问题:
import functools
def debug_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"调用函数: {func.__name__},参数: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"函数返回: {result}")
return result
return wrapper
@debug_decorator
def multiply(x, y):
"""计算两个数的乘积"""
return x * y
print(multiply.__name__) # 输出: multiply
print(multiply.__doc__) # 输出: 计算两个数的乘积
带参数的装饰器
装饰器工厂
有时我们需要让装饰器本身接受参数,这需要创建一个装饰器工厂函数:
def repeat(num_times):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"你好, {name}!")
greet("Alice")
输出:
你好, Alice!
你好, Alice!
你好, Alice!
类装饰器
除了函数装饰器,Python还支持类装饰器。类装饰器需要实现__call__方法:
class CountCalls:
def __init__(self, func):
self.func = func
self.num_calls = 0
functools.update_wrapper(self, func)
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_hello():
print("大家好!")
say_hello()
say_hello()
输出:
函数 say_hello 被调用了 1 次
大家好!
函数 say_hello 被调用了 2 次
大家好!
实际应用案例
日志记录装饰器
import logging
import time
def log_execution_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
logging.info(f"函数 {func.__name__} 执行耗时: {execution_time:.4f}秒")
return result
return wrapper
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@log_execution_time
def slow_function():
time.sleep(1)
return "完成"
slow_function()
权限验证装饰器
class User:
def __init__(self, username, role):
self.username = username
self.role = role
def require_role(required_role):
def decorator(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if user.role != required_role:
raise PermissionError(f"需要 {required_role} 权限,当前用户权限: {user.role}")
return func(user, *args, **kwargs)
return wrapper
return decorator
@require_role("admin")
def delete_user(user, username):
print(f"用户 {user.username} 删除了用户 {username}")
# 测试
admin_user = User("admin", "admin")
regular_user = User("bob", "user")
delete_user(admin_user, "charlie") # 成功
# delete_user(regular_user, "charlie") # 抛出 PermissionError
缓存装饰器
import time
def cache(seconds):
def decorator_cache(func):
cache_dict = {}
@functools.wraps(func)
def wrapper(*args):
# 检查缓存
current_time = time.time()
if args in cache_dict:
result, timestamp = cache_dict[args]
if current_time - timestamp < seconds:
print("从缓存中获取结果")
return result
# 执行函数并缓存结果
result = func(*args)
cache_dict[args] = (result, current_time)
print("计算新结果并缓存")
return result
return wrapper
return decorator_cache
@cache(5) # 缓存5秒
def expensive_calculation(n):
time.sleep(1) # 模拟耗时计算
return n * n
print(expensive_calculation(4)) # 计算新结果并缓存
time.sleep(2)
print(expensive_calculation(4)) # 从缓存中获取结果
time.sleep(4)
print(expensive_calculation(4)) # 计算新结果并缓存
装饰器的组合使用
多个装饰器的堆叠
Python允许多个装饰器堆叠使用,它们的执行顺序是从下到上:
def decorator_a(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("装饰器A - 开始")
result = func(*args, **kwargs)
print("装饰器A - 结束")
return result
return wrapper
def decorator_b(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("装饰器B - 开始")
Python装饰器是Python语言中一个强大而优雅的特性,它允许开发者在不修改原有函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个高阶函数,它接收一个函数作为参数并返回一个新的函数。这种设计模式遵循了软件工程中的开放-封闭原则,即对扩展开放,对修改封闭。
装饰器的语法使用Python的`@`符号,这使得代码更加简洁和可读。通过装饰器,我们可以轻松实现日志记录、性能测试、权限验证、事务处理等横切关注点(Cross-Cutting Concerns),从而保持核心业务逻辑的清晰和纯粹。
## 装饰器的基本概念
### 什么是装饰器
装饰器是一个可调用对象(callable),它接收一个函数作为参数,并返回一个新的函数。装饰器可以在不修改原函数代码的情况下,为函数添加新的功能。
```python
# 最简单的装饰器示例
def my_decorator(func):
def wrapper():
print("在函数调用前执行")
func()
print("在函数调用后执行")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
# 调用装饰后的函数
say_hello()
输出结果:
在函数调用前执行
Hello!
在函数调用后执行
装饰器的工作原理
当Python解释器遇到@decorator语法时,它实际上执行了以下操作:
# @my_decorator 语法等价于:
def say_hello():
print("Hello!")
say_hello = my_decorator(say_hello)
这意味着装饰器在函数定义时就被调用,并将原函数替换为装饰器返回的新函数。
带参数的函数装饰
处理带参数的函数
当被装饰的函数需要参数时,我们需要在包装函数中也接受相应的参数:
def greet_decorator(func):
def wrapper(*args, **kwargs):
print("准备执行函数...")
result = func(*args, **kwargs)
print("函数执行完成!")
return result
return wrapper
@greet_decorator
def add(a, b):
return a + b
print(add(3, 5))
输出:
准备执行函数...
函数执行完成!
8
保留原函数元信息
默认情况下,装饰器会隐藏原函数的名称和文档字符串。使用functools.wraps可以解决这个问题:
import functools
def debug_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"调用函数: {func.__name__},参数: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"函数返回: {result}")
return result
return wrapper
@debug_decorator
def multiply(x, y):
"""计算两个数的乘积"""
return x * y
print(multiply.__name__) # 输出: multiply
print(multiply.__doc__) # 输出: 计算两个数的乘积
带参数的装饰器
装饰器工厂
有时我们需要让装饰器本身接受参数,这需要创建一个装饰器工厂函数:
def repeat(num_times):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"你好, {name}!")
greet("Alice")
输出:
你好, Alice!
你好, Alice!
你好, Alice!
类装饰器
除了函数装饰器,Python还支持类装饰器。类装饰器需要实现__call__方法:
class CountCalls:
def __init__(self, func):
self.func = func
self.num_calls = 0
functools.update_wrapper(self, func)
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_hello():
print("大家好!")
say_hello()
say_hello()
输出:
函数 say_hello 被调用了 1 次
大家好!
函数 say_hello 被调用了 2 次
大家好!
实际应用案例
日志记录装饰器
import logging
import time
def log_execution_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
logging.info(f"函数 {func.__name__} 执行耗时: {execution_time:.4f}秒")
return result
return wrapper
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@log_execution_time
def slow_function():
time.sleep(1)
return "完成"
slow_function()
权限验证装饰器
class User:
def __init__(self, username, role):
self.username = username
self.role = role
def require_role(required_role):
def decorator(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if user.role != required_role:
raise PermissionError(f"需要 {required_role} 权限,当前用户权限: {user.role}")
return func(user, *args, **kwargs)
return wrapper
return decorator
@require_role("admin")
def delete_user(user, username):
print(f"用户 {user.username} 删除了用户 {username}")
# 测试
admin_user = User("admin", "admin")
regular_user = User("bob", "user")
delete_user(admin_user, "charlie") # 成功
# delete_user(regular_user, "charlie") # 抛出 PermissionError
缓存装饰器
import time
def cache(seconds):
def decorator_cache(func):
cache_dict = {}
@functools.wraps(func)
def wrapper(*args):
# 检查缓存
current_time = time.time()
if args in cache_dict:
result, timestamp = cache_dict[args]
if current_time - timestamp < seconds:
print("从缓存中获取结果")
return result
# 执行函数并缓存结果
result = func(*args)
cache_dict[args] = (result, current_time)
print("计算新结果并缓存")
return result
return wrapper
return decorator_cache
@cache(5) # 缓存5秒
def expensive_calculation(n):
time.sleep(1) # 模拟耗时计算
return n * n
print(expensive_calculation(4)) # 计算新结果并缓存
time.sleep(2)
print(expensive_calculation(4)) # 从缓存中获取结果
time.sleep(4)
print(expensive_calculation(4)) # 计算新结果并缓存
装饰器的组合使用
多个装饰器的堆叠
Python允许多个装饰器堆叠使用,它们的执行顺序是从下到上:
def decorator_a(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("装饰器A - 开始")
result = func(*args, **kwargs)
print("装饰器A - 结束")
return result
return wrapper
def decorator_b(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("装饰器B - 开始")
result = func(*args, **kwargs)
print("装饰器B - 结束")
return result
return wrapper
@decorator_a
@decorator_b
def my_function():
print("执行核心函数")
my_function()
输出:
装饰器A - 开始
装饰器B - 开始
执行核心函数
装饰器B - 结束
装饰器A - 结束
类方法装饰器
装饰器同样适用于类的方法:
class Calculator:
@staticmethod
@log_execution_time
def slow_add(a, b):
time.sleep(0.5)
return a + b
@require_role("admin")
def reset(self, user):
print(f"用户 {user.username} 重置了计算器")
calc = Calculator()
print(Calculator.slow_add(2, 3))
admin = User("admin", "admin")
calc.reset(admin)
高级装饰器技巧
使用装饰器实现单例模式
def singleton(cls):
instances = {}
@functools.wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class DatabaseConnection:
def __init__(self):
print("建立数据库连接...")
# 无论创建多少次,都返回同一个实例
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True
装饰器与上下文管理器结合
def transactional(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("开始事务")
try:
result = func(*args, **kwargs)
print("提交事务")
return result
except Exception as e:
print(f"回滚事务: {e}")
raise
return wrapper
@transactional
def transfer_money(from_account, to_account, amount):
print(f"从 {from_account} 转账 {amount} 到 {to_account}")
# 模拟可能失败的操作
if amount < 0:
raise ValueError("金额不能为负")
return True
transfer_money("A", "B", 100)
# transfer_money("A", "B", -50) # 会回滚
装饰器的最佳实践
1. 保持装饰器的简单性
装饰器应该专注于单一职责,避免过于复杂。如果逻辑太复杂,考虑将其拆分为多个装饰器。
2. 使用functools.wraps
始终使用functools.wraps来保留原函数的元信息,这对于调试和文档生成非常重要。
3. 考虑装饰器的性能影响
装饰器会增加函数调用的开销,特别是在嵌套使用时。对于性能敏感的代码,需要评估装饰器带来的影响。
4. 提供清晰的错误信息
在装饰器中添加适当的错误处理和日志,帮助开发者理解问题。
def validate_input(*arg_types):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if len(args) != len(arg_types):
raise ValueError(f"期望 {len(arg_types)} 个参数,但得到 {len(args)}")
for i, (arg, expected_type) in enumerate(zip(args, arg_types)):
if not isinstance(arg, expected_type):
raise TypeError(f"参数 {i} 应该是 {expected_type.__name__},但得到 {type(arg).__name__}")
return func(*args, **kwargs)
return wrapper
return decorator
@validate_input(int, int)
def add_numbers(a, b):
return a + b
# add_numbers("1", 2) # TypeError
总结
Python装饰器是一个强大而灵活的工具,它允许我们以声明式的方式为函数添加功能。通过本文的详细介绍和丰富的代码示例,我们学习了:
- 装饰器的基本概念:理解装饰器作为高阶函数的工作原理
- 基础装饰器实现:如何创建和使用简单的装饰器
- 带参数的装饰器:通过装饰器工厂实现参数化装饰
- 类装饰器:使用类来实现更复杂的装饰逻辑
- 实际应用案例:日志记录、权限验证、缓存等实用场景
- 装饰器组合:多个装饰器的堆叠使用
- 高级技巧:单例模式、事务处理等高级应用
- 最佳实践:编写高质量装饰器的指导原则
掌握装饰器不仅能提高代码的可读性和可维护性,还能让我们更好地理解Python的函数式编程特性。在实际开发中,合理使用装饰器可以大大减少代码重复,提高开发效率。
记住,装饰器的核心思想是”在不修改原有代码的基础上扩展功能”,这正是软件工程中”开放-封闭原则”的完美体现。随着对装饰器理解的深入,你会发现它在Python编程中的无限可能。
