引言:增强函数而不修改其代码
在Python编程中,我们经常需要在不修改函数源代码的情况下,为函数添加额外的功能,如日志记录、性能测试、权限校验等。函数装饰器(Decorator) 正是为了解决这类问题而设计的强大工具。
装饰器是Python的一种语法糖,它允许我们通过@符号来修饰函数或方法,从而优雅地扩展其功能。理解装饰器不仅有助于编写更简洁、更可复用的代码,也是深入理解Python高级特性的重要一步。
在本节中,我们将全面探讨Python函数装饰器的原理、实现和应用,并通过丰富的示例展示其强大功能。
第一部分:装饰器基础
1.1 什么是装饰器?
装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。装饰器的作用是在不修改原函数代码的情况下,为函数添加额外的功能。
1.2 装饰器的基本语法
def decorator_func(original_func):
def wrapper_func(*args, **kwargs):
# 在调用原函数前执行的代码
result = original_func(*args, **kwargs)
# 在调用原函数后执行的代码
return result
return wrapper_func
# 使用装饰器
@decorator_func
def my_function():
pass
上面的@decorator_func语法等价于:
my_function = decorator_func(my_function)
1.3 简单装饰器示例
def simple_decorator(func):
"""一个简单的装饰器,记录函数调用"""
def wrapper():
print(f"开始执行 {func.__name__}")
result = func()
print(f"完成执行 {func.__name__}")
return result
return wrapper
@simple_decorator
def say_hello():
print("Hello, World!")
# 调用被装饰的函数
say_hello()
# 输出:
# 开始执行 say_hello
# Hello, World!
# 完成执行 say_hello
第二部分:装饰器的工作原理
2.1 装饰器的执行时机
装饰器在函数定义时立即执行,而不是在函数调用时执行:
def decorator(func):
print(f"装饰器正在装饰: {func.__name__}")
def wrapper():
print(" wrapper 被调用")
return func()
return wrapper
@decorator
def my_function():
print(" my_function 被调用")
print("函数定义完成")
# 调用函数
my_function()
# 输出:
# 装饰器正在装饰: my_function
# 函数定义完成
# wrapper 被调用
# my_function 被调用
2.2 保留函数元信息
使用装饰器后,原函数的元信息(如函数名、文档字符串等)会被包装函数的元信息所覆盖。可以使用functools.wraps来保留原函数的元信息:
import functools
def preserve_metadata_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""包装函数的文档字符串"""
print(f"调用函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@preserve_metadata_decorator
def example_function():
"""这是一个示例函数的文档字符串"""
print("函数执行中")
print(example_function.__name__) # 输出: example_function
print(example_function.__doc__) # 输出: 这是一个示例函数的文档字符串
2.3 装饰器链
多个装饰器可以堆叠使用,它们按照从下到上的顺序应用:
def decorator1(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("装饰器1 - 前")
result = func(*args, **kwargs)
print("装饰器1 - 后")
return result
return wrapper
def decorator2(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("装饰器2 - 前")
result = func(*args, **kwargs)
print("装饰器2 - 后")
return result
return wrapper
@decorator1
@decorator2
def my_function():
print("函数执行")
my_function()
# 输出:
# 装饰器1 - 前
# 装饰器2 - 前
# 函数执行
# 装饰器2 - 后
# 装饰器1 - 后
上面的代码等价于:
my_function = decorator1(decorator2(my_function))
第三部分:带参数的装饰器
3.1 装饰器工厂函数
如果需要向装饰器传递参数,需要创建一个返回装饰器的函数(装饰器工厂):
def repeat(num_times):
"""装饰器工厂,返回一个装饰器"""
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper_repeat
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"Hello {name}")
greet("Alice")
# 输出:
# Hello Alice
# Hello Alice
# Hello Alice
3.2 带参数的装饰器示例
def log_with_level(level="INFO"):
"""带日志级别的装饰器"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"[{level}] 调用函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
return decorator
@log_with_level(level="DEBUG")
def debug_function():
print("调试函数执行")
@log_with_level(level="ERROR")
def error_function():
print("错误函数执行")
debug_function() # 输出: [DEBUG] 调用函数: debug_function
error_function() # 输出: [ERROR] 调用函数: error_function
3.3 可选参数的装饰器
可以创建既支持带参数使用,也支持不带参数使用的装饰器:
def flexible_decorator(func=None, *, prefix="DEFAULT"):
"""灵活的装饰器,支持带参数和不带参数使用"""
if func is None:
# 带参数调用
return lambda f: flexible_decorator(f, prefix=prefix)
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"[{prefix}] 调用函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
# 不带参数使用
@flexible_decorator
def function1():
print("函数1执行")
# 带参数使用
@flexible_decorator(prefix="CUSTOM")
def function2():
print("函数2执行")
function1() # 输出: [DEFAULT] 调用函数: function1
function2() # 输出: [CUSTOM] 调用函数: function2
第四部分:类装饰器
4.1 作为类的装饰器
除了函数,类也可以作为装饰器使用。类装饰器需要实现__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.num_calls} of {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello() # 输出: 调用 #1 of say_hello
say_hello() # 输出: 调用 #2 of say_hello
print(f"总调用次数: {say_hello.num_calls}") # 输出: 总调用次数: 2
4.2 带参数的类装饰器
类装饰器也可以接受参数:
class Repeat:
"""带参数的类装饰器"""
def __init__(self, num_times):
self.num_times = num_times
def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(self.num_times):
result = func(*args, **kwargs)
return result
return wrapper
@Repeat(num_times=3)
def greet(name):
print(f"Hello {name}")
greet("Bob") # 输出三次: Hello Bob
第五部分:内置装饰器
Python提供了一些有用的内置装饰器:
5.1 @staticmethod
将方法转换为静态方法,不需要接收self参数:
class MathUtils:
@staticmethod
def add(x, y):
return x + y
# 无需创建实例即可调用
result = MathUtils.add(5, 3) # 输出: 8
5.2 @classmethod
将方法转换为类方法,第一个参数是类本身(cls)而不是实例(self):
class Person:
count = 0
def __init__(self, name):
self.name = name
Person.count += 1
@classmethod
def get_count(cls):
return cls.count
person1 = Person("Alice")
person2 = Person("Bob")
print(Person.get_count()) # 输出: 2
5.3 @property
将方法转换为属性,可以定义getter、setter和deleter:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""半径的getter"""
return self._radius
@radius.setter
def radius(self, value):
"""半径的setter"""
if value < 0:
raise ValueError("半径不能为负")
self._radius = value
@property
def area(self):
"""只读属性:面积"""
return 3.14159 * self._radius ** 2
circle = Circle(5)
print(circle.radius) # 输出: 5
print(circle.area) # 输出: 78.53975
circle.radius = 10
print(circle.area) # 输出: 314.159
# circle.area = 100 # 错误: area是只读属性
第六部分:装饰器的实际应用
6.1 日志记录装饰器
import time
import functools
from datetime import datetime
def log_execution(func):
"""记录函数执行日志的装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
print(f"[{datetime.now()}] 开始执行 {func.__name__}")
result = func(*args, **kwargs)
end_time = time.time()
duration = end_time - start_time
print(f"[{datetime.now()}] 完成执行 {func.__name__}, 耗时: {duration:.4f}秒")
return result
return wrapper
@log_execution
def complex_calculation():
"""模拟复杂计算"""
time.sleep(1)
return 42
result = complex_calculation()
6.2 性能分析装饰器
def profile_performance(func):
"""性能分析装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
import cProfile
import pstats
import io
# 创建性能分析器
profiler = cProfile.Profile()
profiler.enable()
result = func(*args, **kwargs)
profiler.disable()
# 输出性能分析结果
s = io.StringIO()
ps = pstats.Stats(profiler, stream=s).sort_stats('cumulative')
ps.print_stats()
print(s.getvalue())
return result
return wrapper
@profile_performance
def performance_test():
"""性能测试函数"""
total = 0
for i in range(100000):
total += i
return total
performance_test()
6.3 缓存装饰器
def cache_results(func):
"""缓存函数结果的装饰器"""
cache = {}
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 创建缓存键
key = str(args) + str(kwargs)
# 检查缓存
if key in cache:
print(f"使用缓存结果: {key}")
return cache[key]
# 计算并缓存结果
result = func(*args, **kwargs)
cache[key] = result
print(f"计算结果并缓存: {key}")
return result
# 提供清理缓存的方法
wrapper.clear_cache = lambda: cache.clear()
return wrapper
@cache_results
def expensive_computation(x, y):
"""模拟昂贵计算"""
time.sleep(1)
return x * y
# 第一次调用会计算
result1 = expensive_computation(5, 6) # 输出: 计算结果并缓存: (5, 6){}
# 第二次相同参数调用会使用缓存
result2 = expensive_computation(5, 6) # 输出: 使用缓存结果: (5, 6){}
# 清理缓存
expensive_computation.clear_cache()
6.4 权限检查装饰器
def require_role(allowed_roles):
"""基于角色的权限检查装饰器"""
def decorator(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if user.get('role') not in allowed_roles:
raise PermissionError(f"用户 {user.get('name')} 没有执行 {func.__name__} 的权限")
return func(user, *args, **kwargs)
return wrapper
return decorator
# 用户数据
admin_user = {'name': 'Alice', 'role': 'admin'}
regular_user = {'name': 'Bob', 'role': 'user'}
@require_role(['admin'])
def delete_user(executor, user_to_delete):
print(f"{executor['name']} 删除了用户 {user_to_delete}")
return True
# 管理员可以执行
delete_user(admin_user, "Charlie") # 成功
# 普通用户不能执行
try:
delete_user(regular_user, "Dave") # 抛出 PermissionError
except PermissionError as e:
print(e)
6.5 重试机制装饰器
def retry_on_failure(max_attempts=3, delay=1, exceptions=(Exception,)):
"""失败重试装饰器"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except exceptions as e:
attempts += 1
if attempts == max_attempts:
print(f"函数 {func.__name__} 在 {max_attempts} 次尝试后仍失败")
raise
print(f"函数 {func.__name__} 第 {attempts} 次尝试失败: {e}, {max_attempts - attempts} 次重试剩余")
time.sleep(delay)
return wrapper
return decorator
@retry_on_failure(max_attempts=3, delay=0.5, exceptions=(ValueError,))
def unreliable_function():
"""模拟不可靠的函数,可能随机失败"""
import random
if random.random() < 0.7: # 70%的概率失败
raise ValueError("随机失败")
return "成功"
# 测试重试机制
try:
result = unreliable_function()
print(f"最终结果: {result}")
except ValueError as e:
print(f"所有尝试都失败了: {e}")
第七部分:装饰器的高级用法
7.1 装饰器注册表
可以创建装饰器来自动注册函数:
# 函数注册表
FUNCTION_REGISTRY = {}
def register_function(name=None):
"""函数注册装饰器"""
def decorator(func):
registry_name = name or func.__name__
FUNCTION_REGISTRY[registry_name] = func
return func
return decorator
@register_function("add")
def add_numbers(a, b):
return a + b
@register_function("multiply")
def multiply_numbers(a, b):
return a * b
# 使用注册表
print(FUNCTION_REGISTRY) # 输出: {'add': <function add_numbers>, 'multiply': <function multiply_numbers>}
result = FUNCTION_REGISTRY['add'](5, 3) # 输出: 8
7.2 基于装饰器的插件系统
# 插件系统
PLUGINS = {}
def register_plugin(func):
"""插件注册装饰器"""
PLUGINS[func.__name__] = func
return func
@register_plugin
def plugin_one():
return "插件一执行"
@register_plugin
def plugin_two():
return "插件二执行"
# 执行所有插件
def run_all_plugins():
results = []
for name, plugin in PLUGINS.items():
results.append(f"{name}: {plugin()}")
return results
print(run_all_plugins()) # 输出: ['plugin_one: 插件一执行', 'plugin_two: 插件二执行']
7.3 装饰器与异步函数
装饰器也可以用于异步函数:
import asyncio
import functools
def async_timer(func):
"""异步函数计时装饰器"""
@functools.wraps(func)
async def wrapper(*args, **kwargs):
start_time = time.time()
result = await func(*args, **kwargs)
end_time = time.time()
print(f"异步函数 {func.__name__} 执行时间: {end_time - start_time:.4f}秒")
return result
return wrapper
@async_timer
async def async_task():
"""模拟异步任务"""
await asyncio.sleep(1)
return "任务完成"
# 运行异步函数
async def main():
result = await async_task()
print(result)
asyncio.run(main())
第八部分:装饰器的最佳实践
8.1 使用functools.wraps
始终使用@functools.wraps来保留原函数的元信息:
import functools
def good_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 装饰器逻辑
return func(*args, **kwargs)
return wrapper
8.2 避免装饰器副作用
装饰器不应该有副作用,特别是避免在装饰时执行逻辑(除了返回包装函数):
# 不好的做法:装饰时有副作用
def bad_decorator(func):
print(f"装饰 {func.__name__}") # 副作用:在导入时就会执行
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
# 好的做法:装饰时无副作用
def good_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
8.3 保持装饰器简洁
装饰器应该专注于单一职责,避免过于复杂:
# 不好的做法:一个装饰器做多件事
def complex_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 日志记录
print(f"调用 {func.__name__}")
# 性能监控
start_time = time.time()
# 权限检查
if not user_has_permission():
raise PermissionError("无权限")
# 执行函数
result = func(*args, **kwargs)
# 更多逻辑...
end_time = time.time()
print(f"耗时: {end_time - start_time}秒")
return result
return wrapper
# 好的做法:多个单一职责的装饰器
@log_call
@check_permission
@measure_time
def my_function():
pass
8.4 考虑使用类装饰器
对于需要维护状态的装饰器,考虑使用类装饰器:
class StatefulDecorator:
"""有状态的装饰器"""
def __init__(self, func):
self.func = func
self.call_count = 0
functools.update_wrapper(self, func)
def __call__(self, *args, **kwargs):
self.call_count += 1
print(f"第 {self.call_count} 次调用 {self.func.__name__}")
return self.func(*args, **kwargs)
第九部分:常见问题与解决方案
9.1 装饰器与静态方法
当装饰静态方法时,需要注意装饰器的顺序:
class MyClass:
@staticmethod
@my_decorator # 正确的顺序
def my_method():
pass
# 错误的顺序会导致问题
# @my_decorator
# @staticmethod
# def my_method():
# pass
9.2 装饰器与方法绑定
在类方法中使用装饰器时,需要注意self参数的传递:
def method_decorator(func):
"""适用于方法的装饰器"""
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
print(f"调用方法: {func.__name__}")
return func(self, *args, **kwargs)
return wrapper
class MyClass:
@method_decorator
def my_method(self, x):
return x * 2
9.3 调试装饰器
当装饰器导致问题时,可以使用以下方法调试:
# 临时移除装饰器进行测试
def my_function():
pass
# 原始函数
original_function = my_function.__wrapped__ if hasattr(my_function, '__wrapped__') else my_function
第十部分:总结
Python函数装饰器是一种强大而灵活的工具,它允许我们在不修改原函数代码的情况下,为函数添加额外的功能。通过装饰器,我们可以实现:
- 代码复用:将横切关注点(如日志、权限检查)从业务逻辑中分离
- 功能增强:为函数添加新功能而不修改其源代码
- 代码组织:通过装饰器链清晰地组织多个功能
在实际应用中,装饰器被广泛用于:
- 日志记录和性能监控
- 权限检查和身份验证
- 缓存和记忆化
- 重试机制和错误处理
- 插件系统和函数注册
掌握装饰器不仅有助于编写更简洁、更可维护的代码,也是深入理解Python高级特性的重要一步。通过本节的学习,你应该能够创建和使用各种类型的装饰器,并在实际项目中应用它们。
280

被折叠的 条评论
为什么被折叠?



