1. 装饰器基础概念解析
装饰器(Decorator)是Python中一种强大的语法特性,它允许在不修改原函数代码的情况下,为函数添加额外的功能。这种设计模式在Python社区被称为"语法糖",因为它让代码变得更加简洁优雅。
1.1 什么是装饰器
从本质上讲,装饰器就是一个接受函数作为参数并返回一个新函数的可调用对象。它遵循了面向切面编程(AOP)的思想,将横切关注点(如日志记录、性能测试、事务处理等)与业务逻辑分离。
python复制def my_decorator(func):
def wrapper():
print("函数执行前")
func()
print("函数执行后")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
这段代码展示了最简单的装饰器实现。当调用say_hello()时,实际上执行的是wrapper()函数,它在原函数前后添加了额外的打印语句。
1.2 装饰器的执行时机
理解装饰器的执行时机非常重要。装饰器在函数定义时立即执行,而不是在函数调用时。这意味着装饰器代码只运行一次,即在Python导入模块或执行脚本时。
python复制def decorator(func):
print("装饰器正在装饰函数")
def wrapper():
print("wrapper被调用")
return func()
return wrapper
@decorator
def my_func():
print("原函数被调用")
print("--- 函数调用前 ---")
my_func()
输出结果会显示"装饰器正在装饰函数"在"--- 函数调用前 ---"之前打印,这证明了装饰器在函数定义时就已经执行。
2. 装饰器的进阶用法
2.1 带参数的装饰器
有时我们需要装饰器本身也能接受参数。这种情况下,我们需要在装饰器外再包装一层函数:
python复制def repeat(num_times):
def decorator_repeat(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"Hello {name}")
greet("World")
这个例子中,repeat装饰器接受一个参数num_times,指定函数执行的次数。这种三层嵌套的结构是带参数装饰器的标准写法。
2.2 类装饰器
除了函数,我们还可以用类来实现装饰器。类装饰器通常通过实现__call__方法来实现:
python复制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.num_calls}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello()
say_hello()
这个类装饰器会记录函数被调用的次数。每次调用被装饰的函数时,__call__方法就会被执行。
2.3 多个装饰器的叠加
Python允许在同一个函数上应用多个装饰器,它们会按照从下往上的顺序执行:
python复制def decorator1(func):
def wrapper():
print("装饰器1 - 前")
func()
print("装饰器1 - 后")
return wrapper
def decorator2(func):
def wrapper():
print("装饰器2 - 前")
func()
print("装饰器2 - 后")
return wrapper
@decorator1
@decorator2
def my_func():
print("原函数")
my_func()
输出结果会显示装饰器2先执行,然后是装饰器1。这种顺序有时被称为"洋葱模型"。
3. 装饰器的实际应用场景
3.1 性能测试与计时
装饰器非常适合用于测量函数执行时间:
python复制import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
run_time = end_time - start_time
print(f"函数 {func.__name__!r} 执行时间: {run_time:.4f}秒")
return result
return wrapper
@timer
def waste_some_time(num_times):
for _ in range(num_times):
sum([i**2 for i in range(10000)])
waste_some_time(1000)
这种计时装饰器可以帮助我们快速识别代码中的性能瓶颈。
3.2 缓存与记忆化
装饰器可以实现函数结果的缓存,避免重复计算:
python复制from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(100))
Python标准库中的functools.lru_cache就是一个非常实用的装饰器,它可以自动缓存函数的结果。
3.3 权限验证与访问控制
在Web开发中,装饰器常用于权限验证:
python复制def requires_auth(f):
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
@app.route('/secret')
@requires_auth
def secret_page():
return "这是秘密页面"
这种模式在Flask等Web框架中非常常见,可以优雅地保护需要认证的路由。
4. 装饰器的高级技巧与陷阱
4.1 保留函数元信息
使用装饰器时,原函数的元信息(如__name__、__doc__等)会被包装函数覆盖。可以使用functools.wraps来保留这些信息:
python复制from functools import wraps
def my_decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
print("调用装饰函数")
return f(*args, **kwargs)
return wrapper
@my_decorator
def example():
"""示例函数的文档字符串"""
print("示例函数")
print(example.__name__) # 输出"example"而不是"wrapper"
print(example.__doc__) # 输出"示例函数的文档字符串"
4.2 装饰器与异常处理
装饰器可以统一处理函数抛出的异常:
python复制def handle_exceptions(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except ValueError as e:
print(f"值错误: {e}")
except TypeError as e:
print(f"类型错误: {e}")
except Exception as e:
print(f"未知错误: {e}")
return wrapper
@handle_exceptions
def risky_operation(x):
if x < 0:
raise ValueError("x不能为负数")
return x ** 2
risky_operation(-1)
这种模式在需要统一错误处理的场景下非常有用。
4.3 装饰器的调试技巧
调试装饰器时可能会遇到一些困惑,因为调用栈中会出现wrapper函数。有几种方法可以简化调试:
- 使用functools.wraps保留原函数信息
- 在wrapper函数中添加print语句记录调用信息
- 使用调试器的条件断点功能
- 为wrapper函数添加独特的__name__属性
python复制def debug_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__} 参数: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} 返回: {result}")
return result
wrapper.__name__ = f"debug_{func.__name__}"
return wrapper
5. 装饰器的最佳实践
5.1 何时使用装饰器
装饰器最适合以下场景:
- 横切关注点(如日志、缓存、权限)
- 需要在不修改原函数的情况下添加功能
- 需要将通用功能应用于多个函数
- 需要临时添加或移除功能
5.2 装饰器的命名约定
良好的命名习惯能让代码更易读:
- 装饰器函数通常以"decorator"或动词开头(如@log, @cache)
- 包装函数通常命名为"wrapper"
- 类装饰器通常使用名词命名(如@Timer, @Retry)
5.3 装饰器的性能考量
虽然装饰器很强大,但过度使用可能会影响性能:
- 每个装饰器都会增加一层函数调用
- 复杂的装饰器可能引入显著的运行时开销
- 在性能关键路径上要谨慎使用装饰器
python复制# 不推荐的过度装饰
@log_call
@validate_args
@cache_result
@retry_on_failure
def performance_critical_function():
# 高性能代码
pass
5.4 装饰器的测试策略
测试装饰器时需要特别考虑:
- 测试装饰器本身的功能
- 测试装饰后的函数行为
- 测试装饰器与其它装饰器的交互
- 测试装饰器的边界条件
python复制import unittest
class TestDecorators(unittest.TestCase):
def test_timer_decorator(self):
@timer
def dummy():
pass
with self.assertLogs() as cm:
dummy()
self.assertIn("执行时间", cm.output[0])
6. 装饰器在标准库中的应用
Python标准库中有许多内置装饰器,了解它们可以提升编码效率:
6.1 @property
将方法转换为属性,实现更优雅的属性访问:
python复制class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("半径不能为负")
self._radius = value
@property
def area(self):
return 3.14 * self._radius ** 2
c = Circle(5)
print(c.area) # 像属性一样访问
c.radius = 10 # 像属性一样设置
6.2 @classmethod 和 @staticmethod
定义类方法和静态方法:
python复制class MyClass:
class_var = "类变量"
def __init__(self, value):
self.value = value
@classmethod
def class_method(cls):
print(f"访问类变量: {cls.class_var}")
@staticmethod
def static_method():
print("这是一个静态方法")
MyClass.class_method() # 不需要实例
MyClass.static_method() # 不需要实例
6.3 @functools.cache 和 @functools.lru_cache
提供函数结果的缓存功能:
python复制from functools import cache, lru_cache
@cache # 简单缓存,无大小限制
def factorial(n):
return n * factorial(n-1) if n else 1
@lru_cache(maxsize=32) # 最近最少使用缓存
def fib(n):
return fib(n-1) + fib(n-2) if n > 1 else n
6.4 @contextlib.contextmanager
创建上下文管理器:
python复制from contextlib import contextmanager
@contextmanager
def managed_file(name):
try:
f = open(name, 'w')
yield f
finally:
f.close()
with managed_file('hello.txt') as f:
f.write('Hello, world!')
7. 装饰器的设计模式
装饰器模式是23种经典设计模式之一,Python通过语言特性直接支持这种模式。
7.1 装饰器模式与继承
装饰器提供了继承之外的另一种扩展功能的方式:
- 继承是静态的,装饰是动态的
- 继承会创建新类型,装饰不会
- 装饰可以叠加,继承是单一的
7.2 装饰器与组合模式
装饰器实际上是组合模式的一种特殊实现:
- 装饰器包装了原函数
- 可以在运行时动态添加功能
- 保持了接口的一致性
7.3 装饰器与代理模式
装饰器与代理模式有相似之处:
- 都包装了原对象
- 都可以控制对原对象的访问
- 装饰器更侧重功能的增强,代理更侧重访问控制
8. 装饰器的替代方案
虽然装饰器很强大,但有时其他方法可能更适合:
8.1 高阶函数
直接使用高阶函数而不使用@语法:
python复制def log_call(func):
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
def say_hello():
print("Hello!")
# 手动应用装饰器
say_hello = log_call(say_hello)
say_hello()
8.2 混入类(Mixin)
使用多重继承来实现功能扩展:
python复制class LoggingMixin:
def __call__(self, *args, **kwargs):
print(f"调用 {self.__class__.__name__}")
return super().__call__(*args, **kwargs)
class MyClass(LoggingMixin):
def __call__(self, x):
return x * 2
obj = MyClass()
print(obj(5)) # 输出调用日志和结果
8.3 猴子补丁
直接修改类或模块的现有属性:
python复制def original_func():
print("原始函数")
def patched_func():
print("补丁函数")
original_func()
import module
module.original_func = patched_func
9. 装饰器的未来发展趋势
Python装饰器仍在不断发展中:
9.1 PEP 614 放宽装饰器语法
Python 3.9引入的PEP 614放宽了装饰器的语法限制,允许更多表达式作为装饰器:
python复制# Python 3.9+ 允许
decorators = [decorator1, decorator2]
@decorators[0]
@decorators[1]
def func():
pass
9.2 类型注解与装饰器
随着类型注解的普及,装饰器也需要适应这一变化:
python复制from typing import Callable, TypeVar
T = TypeVar('T')
def debug(func: Callable[..., T]) -> Callable[..., T]:
def wrapper(*args, **kwargs) -> T:
print(f"调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
9.3 异步装饰器
随着异步编程的普及,异步装饰器变得越来越重要:
python复制import asyncio
from functools import wraps
def async_timer(func):
@wraps(func)
async def wrapper(*args, **kwargs):
start = asyncio.get_event_loop().time()
result = await func(*args, **kwargs)
end = asyncio.get_event_loop().time()
print(f"异步函数 {func.__name__} 执行时间: {end-start:.2f}s")
return result
return wrapper
@async_timer
async def async_task():
await asyncio.sleep(1)
return "完成"
asyncio.run(async_task())
10. 装饰器的实际案例分析
10.1 Flask路由装饰器
Flask框架大量使用装饰器来定义路由:
python复制from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return "首页"
@app.route('/user/<username>')
def show_user(username):
return f"用户: {username}"
10.2 Django的@login_required
Django使用装饰器来处理权限控制:
python复制from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
@login_required
def my_view(request):
return HttpResponse("只有登录用户能看到")
10.3 Pytest的fixture装饰器
Pytest测试框架使用装饰器定义测试夹具:
python复制import pytest
@pytest.fixture
def database_connection():
conn = create_connection()
yield conn
conn.close()
def test_query(database_connection):
result = database_connection.query("SELECT 1")
assert result == 1
10.4 Click的命令行装饰器
Click库使用装饰器构建命令行接口:
python复制import click
@click.command()
@click.option('--count', default=1, help='重复次数')
@click.option('--name', prompt='你的名字', help='问候对象')
def hello(count, name):
for _ in range(count):
click.echo(f"Hello, {name}!")
if __name__ == '__main__':
hello()
11. 装饰器的性能优化
11.1 减少装饰器嵌套
每个装饰器都会增加一层函数调用,深度嵌套会影响性能:
python复制# 不推荐
@decorator1
@decorator2
@decorator3
def func():
pass
# 更好的做法是合并装饰器
def combined_decorator(func):
func = decorator1(decorator2(decorator3(func)))
return func
11.2 使用functools.cached_property
Python 3.8引入的cached_property可以缓存实例属性的计算结果:
python复制from functools import cached_property
class DataSet:
def __init__(self, sequence):
self._data = sequence
@cached_property
def stdev(self):
return statistics.stdev(self._data)
@cached_property
def variance(self):
return statistics.variance(self._data)
11.3 避免装饰器中的重复计算
在装饰器中缓存不需要重复计算的值:
python复制def memoizing_decorator(func):
cache = {}
def wrapper(arg):
if arg not in cache:
print(f"计算 {arg}")
cache[arg] = func(arg)
return cache[arg]
return wrapper
11.4 使用__slots__优化装饰器类
对于类装饰器,使用__slots__可以减少内存占用:
python复制class Memoized:
__slots__ = ('func', 'cache')
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, arg):
if arg not in self.cache:
self.cache[arg] = self.func(arg)
return self.cache[arg]
12. 装饰器的调试与测试
12.1 调试装饰器的问题
调试装饰器时可能会遇到一些特殊问题:
- 调用栈显示的是wrapper函数而非原函数
- 断点可能不会在原函数上触发
- 错误信息可能指向装饰器代码而非业务代码
解决方法:
- 使用functools.wraps保留原函数信息
- 在wrapper函数内部设置条件断点
- 使用调试器的"step into"功能仔细跟踪执行流程
12.2 测试装饰器的策略
测试装饰器时需要同时考虑:
- 装饰器本身的逻辑是否正确
- 装饰后的函数行为是否符合预期
- 装饰器是否保留了原函数的签名和文档
- 装饰器在异常情况下的行为
python复制import unittest
from unittest.mock import patch
class TestDecorators(unittest.TestCase):
def test_logging_decorator(self):
@log_calls
def func(x):
return x * 2
with patch('builtins.print') as mocked_print:
result = func(5)
mocked_print.assert_called_with("调用 func 参数: (5,)")
self.assertEqual(result, 10)
12.3 性能分析装饰器
创建一个装饰器来帮助分析函数性能:
python复制import time
import cProfile
import pstats
import io
def profile(func):
def wrapper(*args, **kwargs):
pr = cProfile.Profile()
pr.enable()
result = func(*args, **kwargs)
pr.disable()
s = io.StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')
ps.print_stats()
print(s.getvalue())
return result
return wrapper
@profile
def slow_function():
total = 0
for i in range(100000):
total += i
return total
13. 装饰器的反模式与陷阱
13.1 装饰器顺序错误
多个装饰器的顺序很重要,错误的顺序可能导致意外行为:
python复制# 不正确的顺序
@cache
@log_calls
def expensive_operation():
pass
# 正确的顺序应该是先记录再缓存
@log_calls
@cache
def expensive_operation():
pass
13.2 修改可变默认参数
装饰器中如果修改了可变默认参数,可能导致难以发现的bug:
python复制# 危险的做法
def bad_decorator(func):
def wrapper(arg=[]): # 可变默认参数
arg.append(1) # 修改默认参数
return func(arg)
return wrapper
13.3 破坏函数签名
不使用functools.wraps会导致原函数信息丢失:
python复制def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@decorator
def func(x:int) -> int:
"""文档字符串"""
return x * 2
# 没有使用wraps时
print(func.__name__) # 输出"wrapper"而非"func"
print(func.__doc__) # 输出None而非"文档字符串"
13.4 过度使用装饰器
装饰器虽然强大,但过度使用会使代码难以理解和调试:
python复制# 过度装饰的例子
@log_call
@validate_args
@cache_result
@retry_on_failure
@timeout(5)
def business_logic():
pass
14. 装饰器的创造性应用
14.1 单例模式装饰器
使用装饰器实现单例模式:
python复制def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class Logger:
def __init__(self):
print("创建Logger实例")
def log(self, message):
print(message)
logger1 = Logger()
logger2 = Logger()
print(logger1 is logger2) # 输出True
14.2 重试机制装饰器
实现自动重试失败操作的装饰器:
python复制import time
from functools import wraps
def retry(max_attempts=3, delay=1):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
if attempts == max_attempts:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=5, delay=2)
def unreliable_api_call():
import random
if random.random() < 0.7:
raise ValueError("API调用失败")
return "成功"
14.3 速率限制装饰器
限制函数调用频率的装饰器:
python复制import time
from functools import wraps
def rate_limit(calls_per_second):
min_interval = 1.0 / calls_per_second
def decorator(func):
last_called = 0
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal last_called
elapsed = time.time() - last_called
wait = min_interval - elapsed
if wait > 0:
time.sleep(wait)
last_called = time.time()
return func(*args, **kwargs)
return wrapper
return decorator
@rate_limit(2) # 每秒最多2次调用
def api_request():
print("API请求发送")
14.4 插件系统装饰器
使用装饰器实现简单的插件系统:
python复制PLUGINS = {}
def register_plugin(name):
def decorator(func):
PLUGINS[name] = func
return func
return decorator
@register_plugin("say_hello")
def hello_plugin():
print("Hello from plugin!")
@register_plugin("say_goodbye")
def goodbye_plugin():
print("Goodbye from plugin!")
def run_plugin(name):
if name in PLUGINS:
PLUGINS[name]()
else:
print(f"未知插件: {name}")
run_plugin("say_hello")
run_plugin("say_goodbye")
15. 装饰器的元编程应用
15.1 类装饰器修改类行为
类装饰器可以修改类的属性和方法:
python复制def add_method(cls):
def decorator(func):
setattr(cls, func.__name__, func)
return func
return decorator
@add_method(str)
def shout(self):
return self.upper() + "!!!"
print("hello".shout()) # 输出"HELLO!!!"
15.2 注册子类装饰器
使用装饰器自动注册子类:
python复制class Animal:
_registry = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if hasattr(cls, 'species'):
Animal._registry[cls.species] = cls
def register_animal(species):
def decorator(cls):
cls.species = species
return cls
return decorator
@register_animal('dog')
class Dog(Animal):
pass
@register_animal('cat')
class Cat(Animal):
pass
print(Animal._registry) # 输出{'dog': <class '__main__.Dog'>, 'cat': <class '__main__.Cat'>}
15.3 接口验证装饰器
使用装饰器验证类是否实现了特定接口:
python复制def implements(interface):
def decorator(cls):
for method in interface.__abstractmethods__:
if not hasattr(cls, method):
raise TypeError(f"{cls.__name__} 必须实现 {method} 方法")
return cls
return decorator
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
@implements(Shape)
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
def perimeter(self):
return 2 * 3.14 * self.radius
16. 装饰器的跨领域应用
16.1 科学计算中的装饰器
在科学计算中,装饰器可用于数据验证和单位转换:
python复制def validate_input(*validators):
def decorator(func):
def wrapper(*args):
for arg, validator in zip(args, validators):
if not validator(arg):
raise ValueError(f"无效输入: {arg}")
return func(*args)
return wrapper
return decorator
def is_positive(x):
return x > 0
@validate_input(is_positive, is_positive)
def rectangle_area(length, width):
return length * width
print(rectangle_area(5, 3)) # 正常
print(rectangle_area(-1, 3)) # 引发异常
16.2 机器学习中的装饰器
在机器学习中,装饰器可用于模型训练过程的监控:
python复制def log_training(epoch_interval=10):
def decorator(train_func):
def wrapper(model, dataset, *args, **kwargs):
print(f"开始训练 {model.__class__.__name__}")
for epoch, metrics in enumerate(train_func(model, dataset, *args, **kwargs)):
if epoch % epoch_interval == 0:
print(f"Epoch {epoch}: {metrics}")
print("训练完成")
return model
return wrapper
return decorator
@log_training(epoch_interval=5)
def train_model(model, dataset):
for epoch in range(100):
# 实际训练代码
metrics = {"loss": 1/(epoch+1), "accuracy": epoch/100}
yield metrics
return model
16.3 游戏开发中的装饰器
在游戏开发中,装饰器可用于技能冷却系统:
python复制def cooldown(seconds):
def decorator(func):
func.last_used = 0
def wrapper(*args, **kwargs):
now = time.time()
if now - func.last_used < seconds:
remaining = seconds - (now - func.last_used)
print(f"技能冷却中,还需等待 {remaining:.1f}秒")
return
func.last_used = now
return func(*args, **kwargs)
return wrapper
return decorator
@cooldown(5)
def fireball():
print("发射火球!")
fireball()
fireball() # 立即再次调用会显示冷却信息
time.sleep(6)
fireball() # 现在可以再次使用
17. 装饰器的替代实现
17.1 使用描述符替代装饰器
描述符可以实现类似装饰器的功能:
python复制class LoggedAccess:
def __init__(self, func):
self.func = func
def __get__(self, obj, cls):
if obj is None:
return self
def wrapper(*args, **kwargs):
print(f"调用 {self.func.__name__}")
return self.func(obj, *args, **kwargs)
return wrapper
class MyClass:
@LoggedAccess
def method(self):
print("方法执行")
obj = MyClass()
obj.method() # 输出调用日志
17.2 使用上下文管理器替代装饰器
某些情况下,上下文管理器可以替代装饰器:
python复制import contextlib
@contextlib.contextmanager
def timing_context():
start = time.time()
yield
end = time.time()
print(f"耗时: {end-start:.2f}秒")
# 使用方式
with timing_context():
time.sleep(1)
17.3 使用类装饰器替代函数装饰器
类装饰器提供了另一种组织代码的方式:
python复制class Timer:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
start = time.time()
result = self.func(*args, **kwargs)
end = time.time()
print(f"{self.func.__name__} 执行时间: {end-start:.4f}秒")
return result
@Timer
def long_running_function():
time.sleep(2)
long_running_function()
18. 装饰器的调试工具
18.1 装饰器调试辅助函数
创建一个帮助调试装饰器的工具函数:
python复制def debug_decorator(debug=False):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if debug:
print(f"调试: 调用 {func.__name__}")
print(f"参数: args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
if debug:
print(f"调试: {func.__name__} 返回: {result}")
return result
return wrapper
return decorator
@debug_decorator(debug=True)
def add(a, b):
return a + b
add(2, 3) # 输出调试信息
18.2 跟踪装饰器调用链
创建一个跟踪装饰器调用顺序的工具:
python复制def trace_decorators():
decorator_stack = []
def decorator(func):
decorator_stack.append(func.__name__)
print(f"装饰器栈: {decorator_stack}")
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
decorator_stack.pop()
return result
return wrapper
return decorator
@trace_decorators()
@trace_decorators()
@trace_decorators()
def triple_decorated():
pass
triple_decorated()
18.3 装饰器性能分析工具
创建一个分析装饰器性能影响的工具:
python复制import time
from functools import wraps
def profile_decorator_overhead(n=10000):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
for _ in range(n):
func(*args, **kwargs)
end = time.perf_counter()
avg_time = (end - start) / n
print(f"平均调用时间: {avg_time * 1e6:.2f}微秒")
return func(*args, **kwargs)
return wrapper
return decorator
@profile_decorator_overhead(100000)
def no_op():
pass
no_op()
19. 装饰器的设计原则
19.1 单一职责原则
每个装饰器应该只负责一个功能:
python复制# 不好的设计 - 一个装饰器做多件事
def bad_decorator(func):
def wrapper(*args, **kwargs):
# 记录日志
print(f"调用 {func.__name__}")
# 验证参数
if not all(isinstance(arg, int) for arg in args):
raise TypeError("参数必须为整数")
# 执行函数
return func(*args, **kwargs)
return wrapper
# 好的设计 - 拆分为多个装饰器
def log_call(func):
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
def validate_int_args(func):
def wrapper(*args, **kwargs):
if not all(isinstance(arg, int) for arg in args):
raise TypeError("参数必须为整数")
return func(*args, **kwargs)
return wrapper
@log_call
@validate_int_args
def good_function(a, b):
return a + b
19.2 开闭原则
装饰器应该遵循开闭原则 - 对扩展开放,对修改关闭:
python复制# 基础装饰器
def base_decorator(func):
def wrapper(*args, **kwargs):
print("基础功能")
return func(*args, **kwargs)
return wrapper
# 扩展装饰器而不修改基础装饰器
def extended_decorator(func):
@base_decorator
def wrapper(*args, **kwargs):
print("扩展功能")
return func(*args, **kwargs)
return wrapper
@extended_decorator
def example():
print("示例函数")
example()
19.3 最小惊讶原则
装饰器的行为应该符合用户预期:
python复制# 不符合最小惊讶原则的装饰器
def surprising_decorator(func):
def wrapper(*args, **kwargs):
# 静默修改返回值
result = func(*args, **kwargs)
return str(result).upper()
return wrapper
@surprising_decorator
def add(a, b):
return a + b
print(add(2, 3)) # 输出"5"而非5,令人惊讶
# 符合最小惊讶原则的装饰器
def explicit_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):