Skip to content

07-装饰器边界情况与调试实战

Python 版本要求:Python 3.11+

贯穿项目:Web API 请求处理系统 本节目标:掌握装饰器边界情况,学会真实场景调试排查

代码位置decorators_demo/app/decorators/ch07_edge_cases.pych07_debug_tools.py测试验证uv run pytest tests/test_ch07.py -vAPI 演示uv run uvicorn app.main:app --reload → 访问 http://localhost:8000/api/v1/debug/


概念铺垫

前6章覆盖了装饰器的基础到高级用法。本章聚焦边界情况真实调试场景

┌─────────────────────────────────────────────────────────────┐
│  第 7 章 — 边界情况与调试实战                                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Part A: 边界情况                                            │
│  ─────────────────                                          │
│  7.1 装饰器叠加顺序问题                                       │
│      • 属性冲突                                              │
│      • 异常处理顺序                                          │
│      • @timer/@log_call 顺序陷阱                             │
│                                                             │
│  7.2 装饰器嵌套与元装饰器                                      │
│      • 装饰装饰器                                            │
│      • 装饰器工厂的工厂                                       │
│      • 类装饰器嵌套函数装饰器                                  │
│                                                             │
│  7.3 闭包变量绑定陷阱(进阶)                                  │
│      • late-binding vs early-binding                        │
│      • 装饰器参数捕获时机                                     │
│                                                             │
│  7.4 异步与方法装饰器兼容性                                    │
│      • sync 装饰器包裹 async 函数                             │
│      • 方法装饰器丢失 self                                    │
│      • @classmethod 顺序问题                                 │
│                                                             │
│  Part B: 调试实战                                            │
│  ─────────────────                                          │
│  7.5 函数签名丢失调试                                         │
│  7.6 异常栈被吞掉调试                                         │
│  7.7 循环变量陷阱现场排查                                      │
│  7.8 async await 遗漏排查                                    │
│                                                             │
│  Part C: 综合实践                                            │
│  ─────────────────                                          │
│  7.9 生产级装饰器排查清单                                      │
│  7.10 延伸阅读                                               │
│                                                             │
│  Part D: 生产环境调试实战                                     │
│  ─────────────────                                          │
│  7.11 装饰器日志追踪问题                                       │
│  7.12 装饰器性能瓶颈追踪                                       │
│  7.13 复杂装饰器链的错误定位                                    │
│  7.14 生产环境调试最佳实践                                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

L1 理解层:会用


Part A: 边界情况


7.1 装饰器叠加顺序问题

问题:多个装饰器叠加时,属性冲突、异常处理顺序、执行顺序可能导致意外行为

问题场景

python
# ❌ 问题:两个装饰器都设置 _last_elapsed 属性,内层被覆盖
@timer_a  # 设置 wrapper._last_elapsed
@timer_b  # 也设置 wrapper._last_elapsed
def process():
    return "done"

process()
print(process._last_elapsed)  # 值是哪个?

根因分析

┌─────────────────────────────────────────────────────────────┐
│  装饰器叠加顺序 — 属性冲突                                      │
│                                                             │
│  @timer_a                                                   │
│  @timer_b                                                   │
│  def process(): ...                                         │
│                                                             │
│  定义阶段(从下往上包装):                                     │
│  ───────────────────────────                               │
│  process()              ← 原函数                            │
│    ↓                                                        │
│  timer_b(process)       ← 内层包装                           │
│    ↓                                                        │
│  wrapper_b              ← 设置 wrapper_b._last_elapsed      │
│    ↓                                                        │
│  timer_a(wrapper_b)     ← 外层包装                           │
│    ↓                                                        │
│  wrapper_a              ← 设置 wrapper_a._last_elapsed      │
│                                                             │
│  最终:process 指向 wrapper_a                                │
│  process._last_elapsed = wrapper_a._last_elapsed           │
│                                                             │
│  ⚠️ 问题:wrapper_b._last_elapsed 无法访问!                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

错误示例

python
# ch07_edge_cases.py
import functools
import time

def timer_a(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        wrapper._last_elapsed = time.perf_counter() - start
        wrapper._source = "timer_a"
        return result
    wrapper._last_elapsed = 0.0
    wrapper._source = "timer_a"
    return wrapper

def timer_b(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        wrapper._last_elapsed = time.perf_counter() - start
        wrapper._source = "timer_b"
        return result
    wrapper._last_elapsed = 0.0
    wrapper._source = "timer_b"
    return wrapper

@timer_a
@timer_b
def process_wrong():
    time.sleep(0.01)
    return "done"

# 测试
result = process_wrong()
print(f"elapsed: {process_wrong._last_elapsed}")  # timer_a 的值
print(f"source: {process_wrong._source}")        # timer_a
# 无法访问 timer_b 的计时!

修复方案

方案1:使用不同属性名

python
def timer_outer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        wrapper._outer_elapsed = time.perf_counter() - start
        return result
    wrapper._outer_elapsed = 0.0
    return wrapper

def timer_inner(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        wrapper._inner_elapsed = time.perf_counter() - start
        return result
    wrapper._inner_elapsed = 0.0
    return wrapper

@timer_outer
@timer_inner
def process_fixed():
    time.sleep(0.01)
    return "done"

# 可以分别访问
process_fixed()
print(f"outer: {process_fixed._outer_elapsed}")
print(f"inner: {process_fixed._inner_elapsed}")

方案2:通过 wrapped 链访问

python
def get_inner_elapsed(func):
    """通过 __wrapped__ 链获取内层属性"""
    inner = getattr(func, "__wrapped__", None)
    if inner and hasattr(inner, "_last_elapsed"):
        return inner._last_elapsed
    return None

process_wrong()
outer_elapsed = process_wrong._last_elapsed
inner_elapsed = get_inner_elapsed(process_wrong)

异常处理顺序陷阱

┌─────────────────────────────────────────────────────────────┐
│  异常处理顺序陷阱                                              │
│                                                             │
│  @catch_errors                                              │
│  @retry                                                     │
│  def api_call(): ...                                        │
│                                                             │
│  调用顺序:                                                   │
│  ─────────────────                                          │
│  catch_errors.wrapper                                       │
│    ↓                                                        │
│  retry.wrapper                                              │
│    ↓                                                        │
│  api_call                                                   │
│                                                             │
│  问题:                                                       │
│  • retry 内部捕获异常并重试                                   │
│  • catch_errors 也捕获异常                                   │
│  • 结果:retry 的重试日志被 catch_errors 吞掉                  │
│                                                             │
│  修复:                                                       │
│  @retry                                                     │
│  @catch_errors                                              │
│  def api_call(): ...                                        │
│                                                             │
│  这样 retry 捕获并重试,catch_errors 只捕获 retry 放弃后的异常 │
│                                                             │
└─────────────────────────────────────────────────────────────┘
python
# ❌ 错误顺序
@catch_errors  # 外层捕获所有异常
@retry(max_attempts=3)  # 内层重试
def api_call():
    raise ConnectionError("failed")

api_call()  # retry 的重试日志看不到,被 catch_errors 吞掉

# ✅ 正确顺序
@retry(max_attempts=3)  # 外层重试
@catch_errors  # 内层捕获最终异常
def api_call():
    raise ConnectionError("failed")

api_call()  # retry 日志可见,最终异常被 catch_errors 记录

API 端点演示

GET /api/v1/debug/order-conflict

bash
$ curl "http://localhost:8000/api/v1/debug/order-conflict"
{
  "result": "done",
  "outer_elapsed_ms": "12.34",
  "inner_elapsed_ms": "10.56",
  "wrong_order_result": {
    "elapsed_ms": "12.34",
    "inner_inaccessible": true
  }
}

自检问题

  1. @timer_a @timer_b def f(): 最终 f 指向哪个 wrapper?
  2. 如何访问内层装饰器的属性?
  3. @catch_errors @retry@retry @catch_errors 哪个顺序正确?

答案

  1. 指向 wrapper_a(最外层)
  2. 通过 f.__wrapped__._last_elapsed 或使用不同属性名
  3. @retry @catch_errors 正确(retry 外层重试,catch_errors 内层捕获最终异常)

7.2 装饰器嵌套与元装饰器

问题:给装饰器加装饰器、装饰器工厂的工厂等复杂嵌套场景

装饰装饰器

python
# 问题:给装饰器添加日志功能
def log_decorator_usage(decorator_func):
    """元装饰器:记录装饰器的使用"""
    @functools.wraps(decorator_func)
    def meta_wrapper(func):
        print(f"[META] {decorator_func.__name__} 正在装饰 {func.__name__}")
        return decorator_func(func)
    return meta_wrapper

@log_decorator_usage
def my_timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"[TIMER] {func.__name__} 耗时 {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

@my_timer  # 使用被装饰的装饰器
def hello():
    return "world"

# 输出:
# [META] my_timer 正在装饰 hello
# [TIMER] hello 耗时 0.0001s

装饰器工厂的工厂

python
def create_rate_limiter_factory(default_max_calls: int):
    """工厂的工厂:生成不同默认配置的限流装饰器"""
    
    def create_rate_limiter(max_calls: int = default_max_calls):
        """真正的装饰器工厂"""
        def decorator(func):
            calls = 0
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                nonlocal calls
                if calls >= max_calls:
                    raise RuntimeError(f"超限(上限 {max_calls})")
                calls += 1
                return func(*args, **kwargs)
            wrapper._max_calls = max_calls
            wrapper._call_count = lambda: calls
            return wrapper
        return decorator
    
    return create_rate_limiter

# 使用
strict_factory = create_rate_limiter_factory(3)  # 默认上限 3
relaxed_factory = create_rate_limiter_factory(100)  # 默认上限 100

@strict_factory()  # 使用默认上限 3
def api_strict():
    return "ok"

@strict_factory(max_calls=5)  # 自定义上限 5
def api_custom():
    return "ok"

api_strict._max_calls  # 3
api_custom._max_calls  # 5

类装饰器嵌套函数装饰器

python
class CountCalls:
    """类装饰器:计数调用次数"""
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self._func = func
        self._count = 0
    
    def __call__(self, *args, **kwargs):
        self._count += 1
        return self._func(*args, **kwargs)
    
    @property
    def count(self):
        return self._count

def timer(func):
    """函数装饰器:计时"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        wrapper._elapsed = time.perf_counter() - start
        return result
    return wrapper

# 组合使用
@timer
@CountCalls
def combined():
    time.sleep(0.01)
    return "done"

combined()
combined()
print(combined._elapsed)  # timer 的属性
print(combined.count)     # AttributeError! timer 包装后丢失 CountCalls

# ✅ 正确顺序
@CountCalls
@timer
def combined_fixed():
    time.sleep(0.01)
    return "done"

combined_fixed()
print(combined_fixed.count)  # 1
print(combined_fixed._elapsed)  # timer 的属性可通过 __wrapped__ 访问

根因分析

┌─────────────────────────────────────────────────────────────┐
│  类装饰器 + 函数装饰器顺序                                      │
│                                                             │
│  @timer                                                     │
│  @CountCalls                                                │
│  def combined(): ...                                        │
│                                                             │
│  定义阶段:                                                   │
│  combined → CountCalls(combined) → counter_obj             │
│           → timer(counter_obj) → wrapper                    │
│                                                             │
│  最终:combined = wrapper(函数)                             │
│  • combined.count 不存在(wrapper 是函数,没有 count 属性)   │
│  • combined._elapsed 存在                                   │
│                                                             │
│  ✅ 正确顺序:                                                │
│  @CountCalls                                                │
│  @timer                                                     │
│  def combined(): ...                                        │
│                                                             │
│  定义阶段:                                                   │
│  combined → timer(combined) → wrapper                       │
│           → CountCalls(wrapper) → counter_obj               │
│                                                             │
│  最终:combined = counter_obj(类实例)                       │
│  • combined.count 存在                                      │
│  • combined._elapsed 通过 __wrapped__ 访问                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

自检问题

  1. 元装饰器的作用是什么?
  2. 装饰器工厂的工厂解决了什么问题?
  3. 类装饰器和函数装饰器叠加时,哪个应该在最外层?

答案

  1. 给装饰器添加额外功能(如日志、验证)
  2. 提供不同默认配置的装饰器工厂族
  3. 类装饰器在最外层(保持类实例的属性和方法)

7.3 闭包变量绑定陷阱(进阶)

问题:late-binding vs early-binding 导致闭包变量意外共享

第2章已介绍循环变量陷阱,本章深入讲解绑定时机问题。

late-binding vs early-binding

python
# ❌ late-binding:变量在调用时才绑定
def create_callbacks_wrong():
    callbacks = []
    for i in range(3):
        def callback():
            return i * 2  # i 在调用时才查找
        callbacks.append(callback)
    return callbacks

cbs = create_callbacks_wrong()
print([cb() for cb in cbs])  # [4, 4, 4] — 全是 i=2 时的值

# ✅ early-binding:变量在定义时就绑定
def create_callbacks_fixed():
    callbacks = []
    for i in range(3):
        def callback(i=i):  # 默认参数在定义时求值
            return i * 2
        callbacks.append(callback)
    return callbacks

cbs = create_callbacks_fixed()
print([cb() for cb in cbs])  # [0, 2, 4] — 正确

装饰器参数捕获时机

python
# ❌ 问题:装饰器参数在定义时捕获,但状态在调用时共享
config = {"level": "debug"}

def log_with_config(func):
    level = config["level"]  # 定义时捕获 debug
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[{level}] {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_with_config
def process():
    return "done"

config["level"] = "error"  # 改配置
process()  # 还是 [debug] — 装饰器定义时已捕获

# ✅ 修复:每次调用时读取配置
def log_with_dynamic_config(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        level = config["level"]  # 调用时读取
        print(f"[{level}] {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_with_dynamic_config
def process_dynamic():
    return "done"

config["level"] = "error"
process_dynamic()  # [error] — 正确

根因分析

┌─────────────────────────────────────────────────────────────┐
│  变量绑定时机                                                  │
│                                                             │
│  定义时绑定(early-binding):                                 │
│  ───────────────────────────                               │
│  • 默认参数 `func(a, b=b)` 中的 b                            │
│  • 装饰器参数层捕获的值                                        │
│  • 优点:值固定,不受外部变化影响                               │
│  • 缺点:无法动态调整                                          │
│                                                             │
│  调用时绑定(late-binding):                                  │
│  ───────────────────────────                               │
│  • 闭包引用的外层变量                                          │
│  • wrapper 内读取全局变量                                     │
│  • 优点:动态响应外部变化                                       │
│  • 缺点:循环变量共享、意外修改                                  │
│                                                             │
│  选择原则:                                                    │
│  ───────────────────────────                               │
│  • 配置固定 → 定义时绑定                                       │
│  • 配置动态 → 调用时绑定                                       │
│  • 循环中创建 → 默认参数捕获                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

自检问题

  1. 为什么循环中的闭包共享同一个变量?
  2. 如何让装饰器动态读取全局配置?
  3. def callback(i=i):i=i 的含义是什么?

答案

  1. Python 的闭包是 late-binding,调用时查找变量,循环结束后变量固定
  2. 在 wrapper 内部读取全局变量,而非在装饰器参数层捕获
  3. 左边 i 是参数名,右边 i=i 是默认参数,在定义时捕获当前循环变量的值

7.4 异步与方法装饰器兼容性

问题:sync 装饰器包裹 async 函数、方法装饰器丢失 self、@classmethod 顺序

sync 装饰器包裹 async 函数的灾难

python
# ❌ sync 装饰器无法正确处理 async 函数
def sync_timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)  # 没有 await!
        wrapper._elapsed = time.perf_counter() - start
        return result
    return wrapper

@sync_timer
async def async_fetch():
    await asyncio.sleep(0.1)
    return {"status": "ok"}

# 调用
result = await async_fetch()
print(result)  # coroutine 对象,不是 dict!
print(async_fetch._elapsed)  # 0.0001 — 没有 sleep!

# ✅ 正确:使用 async 装饰器
def async_timer(func):
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = await func(*args, **kwargs)  # 正确 await
        wrapper._elapsed = time.perf_counter() - start
        return result
    return wrapper

@async_timer
async def async_fetch_fixed():
    await asyncio.sleep(0.1)
    return {"status": "ok"}

result = await async_fetch_fixed()
print(result)  # {"status": "ok"}
print(async_fetch_fixed._elapsed)  # ~0.1

方法装饰器丢失 self

python
# ❌ 普通装饰器用于方法时,self 处理不当
def log_method(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}, args={args}")
        return func(*args, **kwargs)
    return wrapper

class UserService:
    @log_method
    def get_user(self, user_id):
        return {"id": user_id}

service = UserService()
service.get_user(42)
# 输出:调用 get_user, args=(<UserService>, 42)
# self 正常传递,但日志显示包含 self,可能不期望

# ✅ 更优雅:使用 wrapt 或显式处理 self
def log_method_pretty(func):
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        print(f"[{self.__class__.__name__}] {func.__name__}({args})")
        return func(self, *args, **kwargs)
    return wrapper

class UserServiceFixed:
    @log_method_pretty
    def get_user(self, user_id):
        return {"id": user_id}

service = UserServiceFixed()
service.get_user(42)
# 输出:[UserServiceFixed] get_user((42,))

@classmethod 顺序问题

python
# ❌ 装饰器在 @classmethod 外层
def timer_classmethod(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"耗时: {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

class Config:
    @timer_classmethod  # 装饰器在 @classmethod 外层
    @classmethod
    def load(cls):
        return {"config": "value"}

Config.load()  # TypeError: wrapper() 缺少 cls 参数

# ✅ 正确:装饰器在 @classmethod 内层
class ConfigFixed:
    @classmethod
    @timer_classmethod  # 装饰器在 @classmethod 内层
    def load(cls):
        return {"config": "value"}

ConfigFixed.load()  # 正常

根因分析

┌─────────────────────────────────────────────────────────────┐
│  @classmethod + 装饰器顺序                                     │
│                                                             │
│  ❌ 错误顺序:                                                │
│  @timer_classmethod                                         │
│  @classmethod                                               │
│  def load(cls): ...                                         │
│                                                             │
│  定义阶段:                                                   │
│  load → classmethod(load) → bound_method                    │
│        → timer_classmethod(bound_method) → wrapper          │
│                                                             │
│  调用:Config.load()                                         │
│  实际调用:wrapper()                                          │
│  但 wrapper 期望接收 bound_method,没有 cls 参数              │
│                                                             │
│  ✅ 正确顺序:                                                │
│  @classmethod                                               │
│  @timer_classmethod                                         │
│  def load(cls): ...                                         │
│                                                             │
│  定义阶段:                                                   │
│  load → timer_classmethod(load) → wrapper                   │
│        → classmethod(wrapper) → bound_method                │
│                                                             │
│  调用:ConfigFixed.load()                                    │
│  实际调用:bound_method()                                     │
│  classmethod 正确处理 cls → wrapper(cls) → load(cls)        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

自检问题

  1. sync 装饰器包裹 async 函数会发生什么?
  2. 方法装饰器如何优雅处理 self?
  3. @decorator @classmethod@classmethod @decorator 哪个正确?

答案

  1. 返回 coroutine 对象而非结果,async 函数内部的 await 不执行
  2. 在 wrapper 参数中显式声明 (self, *args, **kwargs),或使用 wrapt 库
  3. @classmethod @decorator 正确(classmethod 在外层先处理绑定)

Part B: 调试实战


7.5 函数签名丢失调试

问题:装饰器导致函数签名丢失,IDE 无法推断类型,help() 显示错误

问题现象

python
# ch07_edge_cases.py
def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper  # 没有 @wraps

@bad_decorator
def add(a: int, b: int) -> int:
    """计算两个数的和"""
    return a + b

# 问题
print(add.__name__)   # "wrapper" — 不是 "add"
print(add.__doc__)    # None — 文档丢失
print(add.__annotations__)  # {} — 类型丢失

# IDE 无法推断 add(1, 2) 的返回值类型
# help(add) 显示 wrapper 的信息,不是 add 的

根因分析

┌─────────────────────────────────────────────────────────────┐
│  函数签名丢失原理                                              │
│                                                             │
│  没有 @wraps:                                                │
│  ─────────────────                                          │
│  def bad_decorator(func):                                   │
│      def wrapper(*args, **kwargs):                          │
│          return func(*args, **kwargs)                       │
│      return wrapper  # ← 返回全新的函数                       │
│                                                             │
│  wrapper 的属性:                                             │
│  • __name__ = "wrapper"                                     │
│  • __doc__ = None                                           │
│  • __annotations__ = {}                                     │
│                                                             │
│  原 func 的属性丢失!                                         │
│                                                             │
│  有 @wraps:                                                 │
│  ─────────────────                                          │
│  def good_decorator(func):                                  │
│      @functools.wraps(func)  # ← 复制 func 的属性            │
│      def wrapper(*args, **kwargs):                          │
│          return func(*args, **kwargs)                       │
│      return wrapper                                         │
│                                                             │
│  wrapper 的属性:                                             │
│  • __name__ = "add" ← 复制自 func                            │
│  • __doc__ = "计算两个数的和" ← 复制自 func                    │
│  • __annotations__ = {"a": int, "b": int, "return": int}    │
│  • __wrapped__ = func ← 保留原函数引用                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

调试工具

python
# ch07_debug_tools.py
import inspect
from typing import Callable

def diagnose_signature(func: Callable) -> dict:
    """诊断函数签名状态"""
    return {
        "name": func.__name__,
        "doc": func.__doc__,
        "annotations": func.__annotations__,
        "has_wrapped": hasattr(func, "__wrapped__"),
        "signature": str(inspect.signature(func)),
        "is_preserved": func.__name__ != "wrapper",
    }

def get_original(func: Callable) -> Callable:
    """获取原始函数(通过 __wrapped__ 链)"""
    while hasattr(func, "__wrapped__"):
        func = func.__wrapped__
    return func

# 使用
info = diagnose_signature(add)
print(info)
# {
#   "name": "wrapper",
#   "doc": None,
#   "annotations": {},
#   "has_wrapped": False,
#   "signature": "(*args, **kwargs)",
#   "is_preserved": False
# }

修复方案

python
import functools
from typing import Callable, TypeVar, ParamSpec

P = ParamSpec("P")
T = TypeVar("T")

def good_decorator(func: Callable[P, T]) -> Callable[P, T]:
    @functools.wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
        return func(*args, **kwargs)
    return wrapper

@good_decorator
def add_fixed(a: int, b: int) -> int:
    """计算两个数的和"""
    return a + b

# 验证
info = diagnose_signature(add_fixed)
print(info)
# {
#   "name": "add_fixed",
#   "doc": "计算两个数的和",
#   "annotations": {"a": int, "b": int, "return": int},
#   "has_wrapped": True,
#   "signature": "(a: int, b: int) -> int",
#   "is_preserved": True
# }

API 端点演示

GET /api/v1/debug/signature-lost

bash
$ curl "http://localhost:8000/api/v1/debug/signature-lost"
{
  "bad_decorator_info": {
    "name": "wrapper",
    "doc": null,
    "annotations": {},
    "is_preserved": false
  },
  "good_decorator_info": {
    "name": "add_fixed",
    "doc": "计算两个数的和",
    "annotations": {"a": "int", "b": "int", "return": "int"},
    "is_preserved": true
  }
}

自检问题

  1. 如何判断函数签名是否被正确保留?
  2. __wrapped__ 属性的作用是什么?
  3. 如何获取被多层装饰器包装的原始函数?

答案

  1. 检查 func.__name__ != "wrapper" 或使用 diagnose_signature()
  2. 保留对原始函数的引用,可用于调试和获取原签名
  3. 循环访问 __wrapped__ 直到没有该属性:get_original(func)

7.6 异常栈被吞掉调试

问题:多层装饰器下异常栈只显示 wrapper,无法定位原函数错误位置

问题现象

python
def catch_errors(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            print(f"Error: {e}")
            return {"error": str(e)}
    return wrapper

def log_call(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@catch_errors
@log_call
def process():
    raise ValueError("数据无效")  # 这行错误被吞掉

result = process()
# 输出:Calling wrapper
# 输出:Error: 数据无效
# 但 traceback 只显示 wrapper 的位置,不知道 process 的哪一行出错

根因分析

┌─────────────────────────────────────────────────────────────┐
│  异常栈被吞掉原理                                              │
│                                                             │
│  问题代码:                                                   │
│  ─────────────────────────                                  │
│  @catch_errors                                              │
│  @log_call                                                  │
│  def process():                                             │
│      raise ValueError("数据无效")                            │
│                                                             │
│  调用链:                                                     │
│  process() → catch_errors.wrapper → log_call.wrapper        │
│            → process body → raise                           │
│                                                             │
│  异常栈:                                                     │
│  ValueError: 数据无效                                        │
│    at log_call.wrapper(...)                                 │
│    at catch_errors.wrapper(...)                             │
│                                                             │
│  ⚠️ 没有 process 的行号!                                    │
│  ⚠️ log_call.wrapper 的行号不是错误位置                       │
│                                                             │
│  原因:                                                       │
│  1. 没有 @wraps,__wrapped__ 链断                            │
│  2. catch_errors 捕获后重新抛出或处理,丢失原始栈              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

调试工具

python
# ch07_debug_tools.py
import sys
import traceback

def diagnose_exception(func: Callable) -> dict:
    """捕获并分析异常栈"""
    try:
        func()
    except Exception as e:
        tb = sys.exc_info()[2]
        frames = traceback.extract_tb(tb)
        return {
            "error": str(e),
            "frames": [
                {"file": f.filename, "line": f.lineno, "func": f.name, "code": f.line}
                for f in frames
            ],
            "has_original": any("process" in f.func for f in frames),
        }
    return {"error": None}

# 使用
info = diagnose_exception(process)
print(info["frames"])
# [
#   {"func": "wrapper", ...},  # log_call 的 wrapper
#   {"func": "wrapper", ...},  # catch_errors 的 wrapper
# ]
# 没有 process 的行!

修复方案

方案1:使用 @wraps + raise from

python
import functools

def catch_errors_fixed(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            # 保留原始异常链
            raise RuntimeError(f"{func.__name__} 失败") from e
    return wrapper

def log_call_fixed(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@catch_errors_fixed
@log_call_fixed
def process_fixed():
    raise ValueError("数据无效")

try:
    process_fixed()
except RuntimeError as e:
    print(e.__cause__)  # ValueError: 数据无效
    # traceback 显示完整链

方案2:记录原始栈到自定义属性

python
def catch_errors_with_trace(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            wrapper._last_error = e
            wrapper._last_traceback = traceback.format_exc()
            return {"error": str(e), "func": func.__name__}
    return wrapper

@catch_errors_with_trace
@log_call_fixed
def process_trace():
    raise ValueError("数据无效")

result = process_trace()
print(process_trace._last_traceback)
# 包含完整的 traceback,包括 process_trace 的行号

API 端点演示

GET /api/v1/debug/stack-swallowed

bash
$ curl "http://localhost:8000/api/v1/debug/stack-swallowed"
{
  "wrong_traceback": {
    "frames": [
      {"func": "wrapper", "line": 5},
      {"func": "wrapper", "line": 10}
    ],
    "has_original_func": false
  },
  "fixed_traceback": {
    "frames": [
      {"func": "wrapper", "line": 5},
      {"func": "process_fixed", "line": 15}  # 原函数行号可见
    ],
    "has_original_func": true
  }
}

自检问题

  1. 为什么异常栈只显示 wrapper?
  2. raise ... from e 的作用是什么?
  3. 如何在不重新抛出的情况下保留完整 traceback?

答案

  1. 没有 @wraps,装饰器覆盖了函数名,Python 无法关联到原函数
  2. 创建异常链,保留原始异常作为 cause,traceback 显示完整信息
  3. 将 traceback.format_exc() 保存到装饰器属性,供后续查看

7.7 循环变量陷阱现场排查

问题:生产环境中所有回调函数返回相同值,排查困难

问题场景

python
# 生产代码:动态创建多个 API 回调
def create_api_handlers():
    handlers = {}
    for endpoint in ["users", "products", "orders"]:
        def handler():
            return f"处理 {endpoint}"
        handlers[endpoint] = handler
    return handlers

apis = create_api_handlers()
print(apis["users"]())   # "处理 orders" — 错误!
print(apis["products"]())  # "处理 orders" — 错误!
print(apis["orders"]())  # "处理 orders" — 正确(碰巧)

现场排查步骤

python
# ch07_debug_tools.py
def inspect_closure_trap(func: Callable) -> dict:
    """检查闭包陷阱"""
    code = func.__code__
    closure = func.__closure__
    
    freevars = list(code.co_freevars) if code.co_freevars else []
    contents = [cell.cell_contents for cell in closure] if closure else []
    
    return {
        "freevars": freevars,
        "contents": contents,
        "trap_detected": len(contents) > 0 and all(
            contents[i] == contents[0] for i in range(len(contents))
        ),
    }

# 排查
apis = create_api_handlers()
for name, handler in apis.items():
    info = inspect_closure_trap(handler)
    print(f"{name}: freevars={info['freevars']}, contents={info['contents']}")
    if info["trap_detected"]:
        print(f"  ⚠️ 闭包陷阱:所有 handler 共享 endpoint={info['contents'][0]}")

# 输出:
# users: freevars=['endpoint'], contents=['orders']
#   ⚠️ 闭包陷阱:所有 handler 共享 endpoint=orders
# products: freevars=['endpoint'], contents=['orders']
#   ⚠️ 闭包陷阱:所有 handler 共享 endpoint=orders
# orders: freevars=['endpoint'], contents=['orders']
#   ⚠️ 闭包陷阱:所有 handler 共享 endpoint=orders

根因分析

┌─────────────────────────────────────────────────────────────┐
│  循环变量陷阱排查                                              │
│                                                             │
│  问题代码:                                                   │
│  for endpoint in ["users", "products", "orders"]:           │
│      def handler():                                         │
│          return f"处理 {endpoint}"                           │
│      handlers[endpoint] = handler                           │
│                                                             │
│  闭包状态:                                                   │
│  ─────────────────                                          │
│  handler.__closure__[0].cell_contents                       │
│                                                             │
│  循环过程:                                                   │
│  endpoint="users"  → handler1.__closure__[0] = 引用 endpoint │
│  endpoint="products" → handler2.__closure__[0] = 引用 endpoint│
│  endpoint="orders"  → handler3.__closure__[0] = 引用 endpoint│
│                                                             │
│  ⚠️ 所有 handler 引用同一个 endpoint 变量                     │
│  循环结束后 endpoint = "orders"                              │
│                                                             │
│  排查工具输出:                                               │
│  freevars=['endpoint']                                      │
│  contents=['orders']  ← 全是同一个值                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

修复方案

python
def create_api_handlers_fixed():
    handlers = {}
    for endpoint in ["users", "products", "orders"]:
        def handler(endpoint=endpoint):  # 默认参数捕获当前值
            return f"处理 {endpoint}"
        handlers[endpoint] = handler
    return handlers

apis = create_api_handlers_fixed()
print(apis["users"]())    # "处理 users" ✓
print(apis["products"]())  # "处理 products" ✓
print(apis["orders"]())   # "处理 orders" ✓

# 排查验证
for name, handler in apis.items():
    info = inspect_closure_trap(handler)
    print(f"{name}: {info}")
    # 注意:使用默认参数后,endpoint 不在 freevars 中
    # freevars=[], contents=[]

API 端点演示

GET /api/v1/debug/closure-trap

bash
$ curl "http://localhost:8000/api/v1/debug/closure-trap"
{
  "wrong_handlers": {
    "users": "处理 orders",
    "products": "处理 orders",
    "orders": "处理 orders"
  },
  "wrong_closure_inspection": {
    "users": {"freevars": ["endpoint"], "contents": ["orders"], "trap_detected": true},
    "products": {"freevars": ["endpoint"], "contents": ["orders"], "trap_detected": true},
    "orders": {"freevars": ["endpoint"], "contents": ["orders"], "trap_detected": true}
  },
  "fixed_handlers": {
    "users": "处理 users",
    "products": "处理 products",
    "orders": "处理 orders"
  }
}

自检问题

  1. 如何判断函数是否陷入闭包陷阱?
  2. cell_contents 显示的值是什么?
  3. 使用默认参数后,为什么 freevars 变空?

答案

  1. 检查多个函数的 cell_contents 是否相同,或使用 inspect_closure_trap()
  2. 闭包捕获变量的当前值(late-binding),循环结束后是最后迭代的值
  3. 默认参数在定义时就绑定值,不需要闭包捕获,变量不在 freevars 中

7.8 async await 遗漏排查

问题:async 装饰器忘记 await,返回 coroutine 对象而非结果

问题场景

python
def async_timer_wrong(func):
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)  # 没有 await!
        wrapper._elapsed = time.perf_counter() - start
        return result
    return wrapper

@async_timer_wrong
async def fetch_data():
    await asyncio.sleep(0.1)
    return {"status": "ok"}

# 调用
result = await fetch_data()
print(type(result))  # coroutine,不是 dict!
print(result)  # <coroutine object fetch_data at 0x...>
# await fetch_data() 得到的是 wrapper 返回的 coroutine

排查步骤

python
# ch07_debug_tools.py
import asyncio
import inspect

def diagnose_async_decorator(func: Callable) -> dict:
    """诊断 async 装饰器问题"""
    is_async = inspect.iscoroutinefunction(func)
    
    if not is_async:
        return {"is_async": False, "issue": "函数不是 async"}
    
    # 尝试调用并检查返回值类型
    async def test_call():
        try:
            result = func()
            if inspect.iscoroutine(result):
                return {"is_async": True, "result_type": "coroutine", "await_missing": True}
            return {"is_async": True, "result_type": type(result).__name__, "await_missing": False}
        except Exception as e:
            return {"is_async": True, "error": str(e)}
    
    # 在同步环境中无法直接运行,返回基本信息
    return {
        "is_async": True,
        "is_coroutinefunction": is_async,
        "check_hint": "调用后检查返回值是否为 coroutine",
    }

# 排查
info = diagnose_async_decorator(fetch_data)
print(info)
# {"is_async": True, "is_coroutinefunction": True}

# 真实调用排查
async def real_test():
    result = await fetch_data()
    if inspect.iscoroutine(result):
        print("⚠️ await 遗漏:返回 coroutine 而非结果")
    else:
        print("✅ 正常:返回实际结果")

# asyncio.run(real_test())
# 输出:⚠️ await 遗漏:返回 coroutine 而非结果

根因分析

┌─────────────────────────────────────────────────────────────┐
│  async await 遗漏原理                                          │
│                                                             │
│  问题代码:                                                   │
│  async def wrapper(*args, **kwargs):                        │
│      result = func(*args, **kwargs)  # 没有 await           │
│      return result                                          │
│                                                             │
│  func 是 async 函数:                                         │
│  func(*args, **kwargs) → 返回 coroutine 对象                 │
│  wrapper 返回 coroutine                                      │
│                                                             │
│  调用者:                                                     │
│  await fetch_data()                                         │
│    ↓                                                         │
│  await wrapper(...)                                         │
│    ↓                                                         │
│  wrapper 返回 coroutine(不是 await func的结果)              │
│    ↓                                                         │
│  await 得到的是 coroutine 对象!                              │
│                                                             │
│  ✅ 正确:                                                    │
│  async def wrapper(*args, **kwargs):                        │
│      result = await func(*args, **kwargs)  # 正确 await     │
│      return result                                          │
│                                                             │
│  调用者:                                                     │
│  await fetch_data()                                         │
│    ↓                                                         │
│  await wrapper(...)                                         │
│    ↓                                                         │
│  await func(...) → 得到实际结果                              │
│    ↓                                                         │
│  返回 dict {"status": "ok"}                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

修复方案

python
def async_timer_fixed(func):
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = await func(*args, **kwargs)  # 正确 await
        wrapper._elapsed = time.perf_counter() - start
        return result
    wrapper._elapsed = 0.0
    return wrapper

@async_timer_fixed
async def fetch_data_fixed():
    await asyncio.sleep(0.1)
    return {"status": "ok"}

async def test_fixed():
    result = await fetch_data_fixed()
    print(type(result))  # dict
    print(result)  # {"status": "ok"}
    print(fetch_data_fixed._elapsed)  # ~0.1

# asyncio.run(test_fixed())

API 端点演示

GET /api/v1/debug/async-await-missing

bash
$ curl "http://localhost:8000/api/v1/debug/async-await-missing"
{
  "wrong_result_type": "coroutine",
  "wrong_elapsed": "0.0001",
  "fixed_result_type": "dict",
  "fixed_result": {"status": "ok"},
  "fixed_elapsed_ms": "100.23"
}

自检问题

  1. 如何判断 async 装饰器是否遗漏 await?
  2. func(*args, **kwargs)await func(*args, **kwargs) 的区别?
  3. 如何在类型层面防止这个问题?

答案

  1. 调用后检查返回值是否为 coroutine 对象(inspect.iscoroutine(result)
  2. 前者返回 coroutine 对象,后者返回实际结果(等待 coroutine 完成)
  3. 使用 Callable[P, Coroutine[Any, Any, T]] 类型注解,IDE 会提示需要 await

Part C: 综合实践


7.9 生产级装饰器排查清单

┌─────────────────────────────────────────────────────────────┐
│  生产级装饰器排查清单                                          │
│                                                             │
│  1. 基础检查                                                  │
│  ─────────────────                                          │
│  □ 是否使用 @functools.wraps(func)?                         │
│  □ wrapper 是否正确 return func() 的结果?                   │
│  □ 是否有 __wrapped__ 属性(调试时可用)?                     │
│                                                             │
│  2. 类型安全                                                 │
│  ─────────────────                                          │
│  □ 是否使用 ParamSpec 保留签名?                              │
│  □ async 装饰器是否正确 await?                               │
│  □ 是否区分 sync 和 async 版本?                              │
│                                                             │
│  3. 顺序问题                                                 │
│  ─────────────────                                          │
│  □ 多层装饰器顺序是否正确(异常处理、属性访问)?               │
│  □ 类装饰器是否在最外层?                                      │
│  □ @classmethod 是否在装饰器外层?                            │
│                                                             │
│  4. 闭包陷阱                                                 │
│  ─────────────────                                          │
│  □ 循环中创建函数是否使用默认参数?                            │
│  □ 装饰器参数是否正确捕获时机?                                │
│  □ 是否检查 cell_contents 共享问题?                          │
│                                                             │
│  5. 方法兼容                                                 │
│  ─────────────────                                          │
│  □ 方法装饰器是否正确处理 self?                               │
│  □ 是否使用 wrapt 自动处理?                                  │
│                                                             │
│  6. 异常处理                                                 │
│  ─────────────────                                          │
│  □ 是否保留原始异常栈?                                       │
│  □ 是否使用 raise ... from e?                               │
│  □ 是否记录完整 traceback?                                   │
│                                                             │
│  7. 属性冲突                                                 │
│  ─────────────────                                          │
│  □ 多个装饰器是否使用不同属性名?                              │
│  □ 是否通过 __wrapped__ 链访问内层属性?                       │
│                                                             │
│  8. 测试验证                                                 │
│  ─────────────────                                          │
│  □ 是否测试 __name__、__doc__、__annotations__?              │
│  □ 是否测试 __wrapped__ 可访问?                              │
│  □ 是否测试 async 版本返回正确类型?                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Part D: 生产环境调试实战

本节目标:通过真实生产环境案例,学习日志分析、性能追踪、错误定位技巧


7.11 生产案例一:装饰器日志追踪问题

场景:生产环境日志显示所有函数调用都来自 wrapper,无法定位问题函数

问题现象

2024-01-15 10:23:45 [INFO] Calling wrapper with args=('user_123',)
2024-01-15 10:23:45 [INFO] wrapper returned in 0.012s
2024-01-15 10:24:12 [ERROR] wrapper failed: ValueError("invalid user_id")
2024-01-15 10:24:12 [INFO] Calling wrapper with args=('order_456',)
...

问题:日志全是 wrapper,无法区分是哪个业务函数出错。

根因分析

┌─────────────────────────────────────────────────────────────┐
│  日志追踪问题原理                                              │
│                                                             │
│  ❌ 错误装饰器:                                               │
│  def log_call(func):                                        │
│      def wrapper(*args, **kwargs):                          │
│          log.info(f"Calling wrapper with args={args}")      │
│          return func(*args, **kwargs)                       │
│      return wrapper                                         │
│                                                             │
│  问题:                                                       │
│  • log.info 硬编码 "wrapper"                                 │
│  • func.__name__ 未使用                                      │
│  • 没有 @wraps,函数名丢失                                     │
│                                                             │
│  ✅ 正确装饰器:                                               │
│  def log_call_fixed(func):                                  │
│      @functools.wraps(func)                                 │
│      def wrapper(*args, **kwargs):                          │
│          log.info(f"Calling {func.__name__} with args={args}")│
│          try:                                               │
│              result = func(*args, **kwargs)                 │
│              log.info(f"{func.__name__} returned in Xs")    │
│              return result                                  │
│          except Exception as e:                             │
│              log.error(f"{func.__name__} failed: {e}")      │
│              raise                                          │
│      return wrapper                                         │
│                                                             │
│  日志输出:                                                   │
│  Calling get_user with args=('user_123',)                   │
│  get_user returned in 0.012s                                │
│  get_user failed: ValueError("invalid user_id")             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

调试步骤

python
# ch07_production.py
import logging
import functools

logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
log = logging.getLogger(__name__)

def log_call_wrong(func):
    """❌ 硬编码 wrapper 名字的装饰器"""
    def wrapper(*args, **kwargs):
        log.info(f"Calling wrapper with args={args}")
        try:
            result = func(*args, **kwargs)
            log.info(f"wrapper returned")
            return result
        except Exception as e:
            log.error(f"wrapper failed: {e}")
            raise
    return wrapper

def log_call_fixed(func):
    """✅ 使用 func.__name__ 记录真实函数名"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        func_name = func.__name__
        log.info(f"Calling {func_name} with args={args}")
        try:
            result = func(*args, **kwargs)
            log.info(f"{func_name} returned")
            return result
        except Exception as e:
            log.error(f"{func_name} failed: {e}")
            raise
    return wrapper

# 生产环境排查
def analyze_logs(log_entries: list[str]) -> dict:
    """分析日志条目,统计 wrapper 问题"""
    wrapper_calls = sum(1 for entry in log_entries if "wrapper" in entry)
    named_calls = sum(1 for entry in log_entries if "Calling " in entry and "wrapper" not in entry)
    
    return {
        "wrapper_calls": wrapper_calls,
        "named_calls": named_calls,
        "has_problem": wrapper_calls > named_calls,
        "recommendation": "检查装饰器是否使用 func.__name__ 而非硬编码 'wrapper'",
    }

修复方案

python
# 生产环境统一日志装饰器
def production_log(
    level: str = "INFO",
    include_args: bool = True,
    include_result: bool = False,
):
    """生产级日志装饰器工厂"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            func_name = func.__name__
            log_func = getattr(logging, level.lower())
            
            # 前置日志
            if include_args:
                log_func(f"[{func_name}] 调用: args={args}, kwargs={kwargs}")
            else:
                log_func(f"[{func_name}] 调用")
            
            try:
                result = func(*args, **kwargs)
                
                # 后置日志
                if include_result:
                    log_func(f"[{func_name}] 返回: {result}")
                else:
                    log_func(f"[{func_name}] 完成")
                
                return result
                
            except Exception as e:
                logging.error(f"[{func_name}] 异常: {type(e).__name__}: {e}")
                raise
        
        # 附加元信息供调试
        wrapper._log_config = {
            "level": level,
            "include_args": include_args,
            "include_result": include_result,
        }
        
        return wrapper
    return decorator

@production_log(level="INFO", include_args=True)
def get_user(user_id: str) -> dict:
    return {"id": user_id, "name": "Alice"}

# 日志输出:
# [get_user] 调用: args=('user_123',), kwargs={}
# [get_user] 完成

自检问题

  1. 生产日志全是 wrapper 时如何排查?
  2. 如何在日志装饰器中附加调试信息?
  3. @production_log(level="DEBUG") 如何灵活配置日志级别?

答案

  1. 检查装饰器是否使用 func.__name__ 而非硬编码字符串
  2. 在 wrapper 上附加 _log_config 属性,供运行时查看配置
  3. 参数化装饰器工厂,接收 levelinclude_args 等配置参数

7.12 生产案例二:装饰器性能瓶颈追踪

场景:API 响应时间异常,怀疑某个装饰器拖慢了整体性能

问题现象

API 响应时间统计:
- /api/users: 平均 120ms(预期 10ms)
- /api/orders: 平均 250ms(预期 20ms)
- /api/products: 平均 80ms(预期 5ms)

怀疑:某个装饰器在每次调用都执行了昂贵的操作

根因分析

┌─────────────────────────────────────────────────────────────┐
│  性能瓶颈追踪原理                                              │
│                                                             │
│  ❌ 性能陷阱装饰器:                                           │
│  def cache_result(func):                                    │
│      cache = {}  # ← 每次调用都创建新字典!                   │
│      def wrapper(*args):                                    │
│          key = str(args)  # ← 每次调用都序列化!              │
│          if key in cache:                                   │
│              return cache[key]                              │
│          result = func(*args)                               │
│          cache[key] = result                                │
│          return result                                      │
│      return wrapper                                         │
│                                                             │
│  问题:                                                       │
│  • cache 在 wrapper 内部,每次调用都新建                      │
│  • str(args) 序列化开销大                                     │
│  • cache 从未真正缓存(每次都是空的)                          │
│                                                             │
│  ✅ 正确缓存装饰器:                                           │
│  import functools                                           │
│                                                             │
│  @functools.lru_cache(maxsize=128)                          │
│  def expensive_operation(n: int) -> int:                    │
│      # 昂贵的计算                                             │
│      return sum(range(n))                                   │
│                                                             │
│  或者手写正确版本:                                            │
│  def cache_result_fixed(func):                              │
│      cache = {}  # ← 在 decorator 层定义,只创建一次          │
│      @functools.wraps(func)                                 │
│      def wrapper(*args):                                    │
│          key = args  # ← 直接用 tuple 作为 key               │
│          if key in cache:                                   │
│              return cache[key]                              │
│          result = func(*args)                               │
│          cache[key] = result                                │
│          return result                                      │
│      wrapper._cache = cache  # ← 供调试访问                  │
│      return wrapper                                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

调试工具

python
# ch07_production.py
import time
import functools
from typing import Callable, Any

class PerformanceTracker:
    """性能追踪器 — 记录每个装饰器的耗时分布"""
    
    def __init__(self):
        self._stats: dict[str, list[float]] = {}
    
    def track(self, func_name: str, elapsed: float) -> None:
        if func_name not in self._stats:
            self._stats[func_name] = []
        self._stats[func_name].append(elapsed)
    
    def get_report(self) -> dict[str, dict[str, float]]:
        report = {}
        for func_name, times in self._stats.items():
            report[func_name] = {
                "count": len(times),
                "total": sum(times),
                "avg": sum(times) / len(times),
                "max": max(times),
                "min": min(times),
            }
        return report
    
    def get_slowest(self, threshold: float = 0.1) -> list[tuple[str, float]]:
        """获取超过阈值的慢调用"""
        slow = []
        for func_name, times in self._stats.items():
            for t in times:
                if t > threshold:
                    slow.append((func_name, t))
        return sorted(slow, key=lambda x: x[1], reverse=True)


tracker = PerformanceTracker()

def track_performance(func: Callable) -> Callable:
    """性能追踪装饰器"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        tracker.track(func.__name__, elapsed)
        wrapper._last_elapsed = elapsed
        return result
    wrapper._last_elapsed = 0.0
    return wrapper


def diagnose_performance(func: Callable, call_count: int = 100) -> dict:
    """诊断函数性能"""
    times = []
    for _ in range(call_count):
        start = time.perf_counter()
        func()
        times.append(time.perf_counter() - start)
    
    return {
        "func_name": func.__name__,
        "call_count": call_count,
        "avg_ms": sum(times) / len(times) * 1000,
        "max_ms": max(times) * 1000,
        "min_ms": min(times) * 1000,
        "variance": (max(times) - min(times)) * 1000,
    }

修复方案

python
# 使用 cProfile 精确分析
import cProfile
import pstats

def profile_decorator(func):
    """性能分析装饰器 — 使用 cProfile"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        profiler = cProfile.Profile()
        profiler.enable()
        result = func(*args, **kwargs)
        profiler.disable()
        
        stats = pstats.Stats(profiler)
        wrapper._profile_stats = stats
        
        # 打印最耗时的 5 个调用
        stats.sort_stats('cumulative')
        stats.print_stats(5)
        
        return result
    return wrapper

@profile_decorator
def slow_function():
    total = 0
    for i in range(100000):
        total += i
    return total

# 输出:
#          5 function calls in 0.005 seconds
#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#         1    0.005    0.005    0.005    0.005 slow_function.py:10(slow_function)

API 端点演示

GET /api/v1/debug/performance-analysis

bash
$ curl "http://localhost:8000/api/v1/debug/performance-analysis"
{
  "tracker_report": {
    "get_user": {"count": 100, "avg_ms": "12.3", "max_ms": "45.6"},
    "get_orders": {"count": 50, "avg_ms": "125.4", "max_ms": "250.1"}
  },
  "slowest_calls": [
    ["get_orders", 0.250],
    ["get_orders", 0.245],
    ["get_user", 0.046]
  ],
  "recommendation": "检查 get_orders 装饰器是否有性能陷阱"
}

自检问题

  1. 如何追踪多个装饰器的性能开销?
  2. 发现某个装饰器拖慢性能时,如何定位根因?
  3. 生产环境如何安全地添加性能追踪?

答案

  1. 使用 PerformanceTracker 收集每次调用的耗时,生成统计报告
  2. 检查 wrapper 内是否有昂贵操作(如序列化、新建对象、数据库查询)
  3. 使用可配置的追踪装饰器,通过参数开关启用/禁用,避免影响正常性能

7.13 生产案例三:复杂装饰器链的错误定位

场景:5层装饰器叠加,错误发生在中间某层,无法快速定位

问题现象

python
@auth_required
@rate_limit
@cache_result
@log_call
@retry
def process_order(order_id: str) -> dict:
    # 业务逻辑
    if order_id == "invalid":
        raise ValueError("订单不存在")
    return {"order_id": order_id, "status": "processed"}

# 调用时出错
process_order("invalid")
# ValueError: 订单不存在
# 但 traceback 只显示 wrapper 行号,不知道是哪一层

根因分析

┌─────────────────────────────────────────────────────────────┐
│  复杂装饰器链错误定位                                          │
│                                                             │
│  装饰器链:                                                    │
│  auth_required → rate_limit → cache_result → log_call → retry│
│                                                             │
│  调用顺序:                                                   │
│  auth_required.wrapper                                      │
│    → rate_limit.wrapper                                     │
│      → cache_result.wrapper                                 │
│        → log_call.wrapper                                   │
│          → retry.wrapper                                    │
│            → process_order                                  │
│              → raise ValueError                             │
│                                                             │
│  traceback 显示:                                             │
│  ValueError: 订单不存在                                       │
│    File "auth_required.py", line 15, in wrapper             │
│    File "rate_limit.py", line 20, in wrapper                │
│    File "cache_result.py", line 25, in wrapper              │
│    File "log_call.py", line 30, in wrapper                  │
│    File "retry.py", line 35, in wrapper                     │
│    File "service.py", line 100, in process_order            │
│                                                             │
│  ⚠️ 虽然有 process_order 行号,但 5 层 wrapper 混淆视线         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

调试工具

python
# ch07_production.py
import traceback
import functools
from typing import Callable, Any

def locate_error_in_chain(func: Callable, *args, **kwargs) -> dict:
    """定位装饰器链中的错误位置"""
    try:
        func(*args, **kwargs)
        return {"success": True}
    except Exception as e:
        tb = traceback.extract_tb(e.__traceback__)
        
        # 分析 traceback
        layers = []
        original_func_line = None
        
        for frame in tb:
            func_name = frame.name
            if func_name == "wrapper":
                # 尝试通过 __wrapped__ 链确定是哪层装饰器
                decorator_name = _identify_decorator(func, frame.lineno)
                layers.append({
                    "type": "decorator",
                    "name": decorator_name or "unknown_decorator",
                    "file": frame.filename,
                    "line": frame.lineno,
                })
            else:
                # 原函数
                original_func_line = {
                    "type": "original",
                    "name": func_name,
                    "file": frame.filename,
                    "line": frame.lineno,
                    "code": frame.line,
                }
                layers.append(original_func_line)
        
        return {
            "success": False,
            "error": str(e),
            "error_type": type(e).__name__,
            "decorator_layers": layers[:-1] if original_func_line else layers,
            "original_location": original_func_line,
        }


def _identify_decorator(func: Callable, line_no: int) -> str | None:
    """通过 __wrapped__ 链识别装饰器"""
    current = func
    while hasattr(current, "__wrapped__"):
        # 检查当前函数的定义文件和行号是否匹配
        code = current.__code__
        if code.co_firstlineno <= line_no <= code.co_firstlineno + 20:
            return getattr(current, "__decorator_name__", current.__name__)
        current = current.__wrapped__
    return None


def named_decorator(name: str) -> Callable:
    """命名装饰器工厂 — 为调试添加标识"""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        wrapper.__decorator_name__ = name
        return wrapper
    return decorator


# 生产环境使用
@named_decorator("auth_required")
@named_decorator("rate_limit")
@named_decorator("cache")
@named_decorator("log")
@named_decorator("retry")
def service_func(data: str) -> str:
    if data == "error":
        raise ValueError("数据处理失败")
    return f"processed: {data}"

# 调试定位
info = locate_error_in_chain(service_func, "error")
print(info["decorator_layers"])
# [
#   {"type": "decorator", "name": "auth_required", ...},
#   {"type": "decorator", "name": "rate_limit", ...},
#   {"type": "decorator", "name": "cache", ...},
#   {"type": "decorator", "name": "log", ...},
#   {"type": "decorator", "name": "retry", ...},
# ]
print(info["original_location"])
# {"type": "original", "name": "service_func", "line": 50, "code": "raise ValueError(...)"}

修复方案

方案1:简化装饰器链

python
# 将通用功能合并到一个装饰器
def combined_decorator(
    require_auth: bool = True,
    rate_limit: int = 100,
    use_cache: bool = True,
    log_calls: bool = True,
    retry_count: int = 3,
):
    """组合装饰器 — 减少嵌套层数"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # auth
            if require_auth:
                _check_auth()
            
            # rate limit
            if rate_limit:
                _check_rate_limit(rate_limit)
            
            # retry + log
            for attempt in range(retry_count):
                try:
                    if log_calls:
                        _log_call(func.__name__, args)
                    
                    # cache
                    if use_cache:
                        cached = _get_cached(func.__name__, args)
                        if cached:
                            return cached
                    
                    result = func(*args, **kwargs)
                    
                    if use_cache:
                        _set_cache(func.__name__, args, result)
                    
                    return result
                    
                except Exception as e:
                    if attempt == retry_count - 1:
                        raise
                    time.sleep(0.1)
        
        wrapper.__decorator_name__ = "combined"
        return wrapper
    return decorator

@combined_decorator(require_auth=True, rate_limit=100)
def simple_service(data: str) -> str:
    return f"processed: {data}"

# 只有 1 层 wrapper,traceback 清晰

方案2:使用结构化日志

python
import structlog

log = structlog.get_logger()

def structured_log_decorator(func):
    """结构化日志装饰器 — 记录完整调用链"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        with log.bind(
            func_name=func.__name__,
            decorator_chain=_get_decorator_chain(func),
        ):
            log.info("decorator_entry")
            try:
                result = func(*args, **kwargs)
                log.info("decorator_exit")
                return result
            except Exception as e:
                log.error("decorator_error", error=str(e))
                raise
    return wrapper

def _get_decorator_chain(func: Callable) -> list[str]:
    """获取装饰器链名称"""
    chain = []
    current = func
    while hasattr(current, "__wrapped__"):
        name = getattr(current, "__decorator_name__", "unknown")
        chain.append(name)
        current = current.__wrapped__
    chain.append(current.__name__)  # 原函数
    return chain

API 端点演示

POST /api/v1/debug/locate-error

bash
$ curl -X POST "http://localhost:8000/api/v1/debug/locate-error" \
  -H "Content-Type: application/json" \
  -d '{"data": "error"}'
{
  "error": "数据处理失败",
  "error_type": "ValueError",
  "decorator_layers": [
    {"name": "auth_required", "line": 15},
    {"name": "rate_limit", "line": 20},
    {"name": "cache", "line": 25},
    {"name": "log", "line": 30},
    {"name": "retry", "line": 35}
  ],
  "original_location": {
    "name": "service_func",
    "line": 50,
    "code": "raise ValueError('数据处理失败')"
  }
}

自检问题

  1. 如何快速定位多层装饰器链中的错误位置?
  2. __decorator_name__ 属性的作用是什么?
  3. 如何简化复杂的装饰器链?

答案

  1. 使用 locate_error_in_chain() 分析 traceback,区分 decorator 层和 original 层
  2. 为 wrapper 添加命名标识,方便调试时识别是哪层装饰器
  3. 将多个功能合并到一个组合装饰器,或使用结构化日志记录调用链

7.14 生产环境调试最佳实践

推荐工具

工具用途示例
structlog结构化日志,记录完整上下文log.bind(decorator_chain=...)
cProfile性能分析,定位热点@profile_decorator
line_profiler逐行性能分析@profile
py-spy生产环境性能采样py-spy top --pid 12345
opentelemetry分布式追踪跨服务装饰器链追踪

调试流程

┌─────────────────────────────────────────────────────────────┐
│  生产环境调试流程                                              │
│                                                             │
│  1. 收集信息                                                  │
│  ─────────────────                                          │
│  • 日志分析:统计 wrapper 出现频率                             │
│  • 性能报告:识别慢调用                                        │
│  • 错误追踪:分析 traceback                                    │
│                                                             │
│  2. 定位问题                                                  │
│  ─────────────────                                          │
│  • 使用 locate_error_in_chain() 定位错误层                    │
│  • 使用 diagnose_performance() 识别性能瓶颈                   │
│  • 使用 inspect_closure_trap() 检查闭包问题                   │
│                                                             │
│  3. 根因分析                                                  │
│  ─────────────────                                          │
│  • 检查装饰器代码(硬编码 wrapper、性能陷阱)                    │
│  • 检查装饰器顺序(类装饰器、@classmethod)                     │
│  • 检查闭包变量绑定时机                                        │
│                                                             │
│  4. 修复验证                                                  │
│  ─────────────────                                          │
│  • 使用 named_decorator 添加标识                             │
│  • 使用 combined_decorator 简化链                             │
│  • 运行 test_ch07.py 验证修复                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

7.10 延伸阅读


L2 实践层:用好

推荐做法

做法原因示例
不同装饰器使用不同属性名避免叠加时属性覆盖_outer_elapsed vs _inner_elapsed
通过 __wrapped__ 链访问内层属性不修改装饰器名称也能访问func.__wrapped__._inner_elapsed
异常重试装饰器放最外层重试逻辑包裹内层的错误处理@retry @catch_errors def f():
类装饰器放最外层保留类实例的属性/方法@CountCalls @timer def f():
@classmethod 放装饰器外层classmethod 先处理 cls 绑定@classmethod @timer def f():
为 wrapper 添加 __decorator_name__调试时识别装饰器层wrapper.__decorator_name__ = "auth"
循环中创建闭包用默认参数early-binding 避免变量共享lambda x, i=i: ...
async 装饰器必须 await否则返回 coroutine 对象result = await func(*args, **kwargs)
生产日志使用 func.__name__避免日志全是 "wrapper"log.info(f"[{func.__name__}] ...")

反模式:不要这样做

python
# ❌ 错误:两个装饰器使用同名属性
@timer_a  # 设置 _last_elapsed
@timer_b  # 也设置 _last_elapsed — 覆盖!
def process(): ...

# ❌ 错误:sync 装饰器包裹 async 函数
def sync_timer(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)  # 没有 await,返回 coroutine
        return result
    return wrapper

# ❌ 错误:装饰器在 @classmethod 外层
@timer
@classmethod
def load(cls): ...  # TypeError: wrapper() missing cls

# ❌ 错误:日志硬编码 "wrapper"
def log_call(func):
    def wrapper(*args, **kwargs):
        log.info("Calling wrapper")  # 永远显示 wrapper
        return func(*args, **kwargs)
    return wrapper

# ❌ 错误:类装饰器在函数装饰器内层
@timer  # 函数装饰器在外层
@CountCalls  # 类装饰器在内层
def f(): ...  # f.count 不存在!wrapper 是函数没有 count 属性

# ❌ 错误:try-except 捕获后吞掉原始 traceback
def catch_errors(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            print(f"Error: {e}")  # 吞掉 traceback!
            return None
    return wrapper

适用场景

场景是否推荐原因
多个装饰器需要暴露属性✅ 用不同属性名避免叠加覆盖
多层装饰器调试__wrapped__逐层访问内层函数和属性
需要同时重试和错误处理@retry @catch_errorsretry 外层重试,catch 内层兜底
复杂装饰器链(5+ 层)⚠️ 考虑合并combined_decorator 减少层数
装饰器方法访问 self✅ wrapt 或显式 (self, ...)wrapt 自动处理,原生需显式声明
装饰器中做耗时初始化❌ 放在 wrapper 外层定义阶段就执行,拖慢模块加载

L3 专家层:深入

Python 如何实现:__wrapped__ 链的内省机制

装饰器链形成后,__wrapped__ 属性构成一个单向链表,每个节点是一个包装函数:

┌─────────────────────────────────────────────────────────────┐
│  __wrapped__ 链结构                                            │
│                                                             │
│  @deco_a                                                    │
│  @deco_b                                                    │
│  @deco_c                                                    │
│  def target():                                              │
│      pass                                                    │
│                                                             │
│  内存中的 __wrapped__ 链:                                    │
│                                                             │
│  wrapper_a.__wrapped__ → wrapper_b                          │
│  wrapper_b.__wrapped__ → wrapper_c                          │
│  wrapper_c.__wrapped__ → target (原函数)                     │
│  target.__wrapped__    → AttributeError(不存在)            │
│                                                             │
│  遍历链获取所有层级:                                          │
│  def walk_chain(func):                                      │
│      chain = []                                              │
│      while hasattr(func, '__wrapped__'):                     │
│          chain.append(func)                                  │
│          func = func.__wrapped__                             │
│      chain.append(func)  # 原函数                            │
│      return chain                                            │
│                                                             │
│  使用 __wrapped__ 绕过装饰器调用:                             │
│  result = target.__wrapped__(*args)  # 直接调用 wrapper_b   │
│  result = walk_chain(target)[-1](*args)  # 直接调用原函数    │
│                                                             │
│  ⚠️ 前提:所有装饰器都使用了 @wraps,否则 __wrapped__ 断开       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

CPython 层面的实现__wrapped__ 不是什么特殊机制——它就是 functools.update_wrapper 中一行 wrapper.__wrapped__ = wrapped 设置的普通属性。CPython 的 inspect 模块在 inspect.unwrap() 中循环读取这个属性来追溯原始函数。

python
# CPython inspect.unwrap 简化版(Lib/inspect.py)
def unwrap(func, *, stop=None):
    while True:
        if stop and stop(func):
            return func
        func = getattr(func, "__wrapped__", None)
        if func is None:
            raise ValueError("wrapper loop when unwrapping")
    # 实际还有循环检测(避免 __wrapped__ 指向自身)

装饰器栈帧与 traceback 构建

当装饰器链中的函数抛出异常时,Python 如何构建 traceback?

┌─────────────────────────────────────────────────────────────┐
│  Traceback 构建过程                                            │
│                                                             │
│  @deco_a                                                    │
│  @deco_b                                                    │
│  def target():                                              │
│      raise ValueError()                                     │
│                                                             │
│  调用 target() 时的调用栈:                                  │
│                                                             │
│  帧 #1: <module>                  line 10: target()         │
│  帧 #2: wrapper_a(args...)        line 5:  return func(...)  │
│  帧 #3: wrapper_b(args...)        line 5:  return func(...)  │
│  帧 #4: target()                  line 3:  raise ValueError  │
│          ← 异常在此抛出                                     │
│                                                             │
│  Python 从帧 #4 向上回溯构建 traceback:                      │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ File "test.py", line 10, in <module>               │   │
│  │   target()                                          │   │
│  │ File "test.py", line 5, in wrapper_a                │   │
│  │   return func(*args, **kwargs)                      │   │
│  │ File "test.py", line 5, in wrapper_b                │   │
│  │   return func(*args, **kwargs)                      │   │
│  │ File "test.py", line 3, in target                   │   │
│  │   raise ValueError()                                 │   │
│  │ ValueError                                          │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  问题:每层 wrapper 的行号是装饰器定义位置,不是错误位置          │
│  解决:使用 @wraps + raise ... from e 保留异常链              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

dis.dis() 验证装饰器字节码

使用 dis 模块可以看到装饰器在字节码层面的表现:

python
import dis

# 定义简单装饰器和函数
def simple_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@simple_decorator
def add(a, b):
    return a + b

# 反汇编被装饰后的函数
print("=== add (decorated) ===")
dis.dis(add)
# 输出:
#   5           0 RESUME                   0
#   6           2 LOAD_DEREF               0 (func)    ← 从闭包加载原函数
#               4 LOAD_FAST                0 (args)
#               6 BUILD_MAP                0
#               8 LOAD_FAST                1 (kwargs)
#              10 BUILD_MAP                0
#              12 CALL_FUNCTION_EX         1           ← 调用 func(*args, **kwargs)
#              14 RETURN_VALUE

# 查看 wrapper 的闭包
print(f"add.__code__.co_freevars: {add.__code__.co_freevars}")
# ('func',)

# 查看原函数
print(f"add.__wrapped__: {add.__wrapped__}")
# <function add at 0x...>
print(f"add.__wrapped__(1, 2): {add.__wrapped__(1, 2)}")
# 3

# 验证 @ 语法糖的等价性
import dis

code = """
@simple_decorator
def add(a, b):
    return a + b
"""

# 编译并反汇编模块级别代码
compiled = compile(code, '<test>', 'exec')
print("=== module-level bytecode ===")
dis.dis(compiled)
# 输出中可以看到:
# LOAD_NAME    'simple_decorator'
# LOAD_CONST   <add 的 code object>
# MAKE_FUNCTION               ← 创建 add 函数对象
# CALL         1              ← 调用 simple_decorator(add)
# STORE_NAME   'add'          ← 重新绑定 add

# 查看是否有 __wrapped__
print("=== wrapped? ===")
# 直接定义的函数没有 __wrapped__
def plain(): pass
print(hasattr(plain, '__wrapped__'))  # False

dis.dis() 调试装饰器的技巧

检查项dis 验证方式期望结果
装饰器是否正确包装dis.dis(func) 看是否有 LOAD_DEREFLOAD_DEREF 说明通过闭包引用原函数
是否使用了 @wrapshasattr(func, '__wrapped__')True — wraps 会设置该属性
闭包捕获是否正确func.__code__.co_freevars包含被捕获的变量名
async 装饰器是否 awaitdis.dis(func) 看是否有 GET_AWAITABLE有 — 说明正确使用了 await
@ 语法降级反汇编模块级代码看到 CALL + STORE_NAME 序列

性能考量

操作时间复杂度说明
func.__wrapped__ 访问O(1)普通属性访问
unwrap(func) 遍历链O(m)m = 装饰器层数
traceback.extract_tb()O(k)k = traceback 帧数(等于装饰器层数 + 1)
dis.dis(func)O(n)n = 字节码指令条数
inspect.signature()O(1)读取 __signature__ 缓存或解析 __annotations__
调试方案开销适用环境
diagnose_signature()~5μs开发/测试
locate_error_in_chain()~50μs*仅在异常发生时
PerformanceTracker~1μs/调用开发/灰度
cProfile~10% 性能损耗开发/测试
py-spy接近零生产环境采样

知识关联

┌─────────────────────────────────────────────────────────────┐
│  知识关联图:装饰器调试 → 性能分析 → 异常追踪                   │
│                                                             │
│  第 7 章:装饰器边界情况与调试实战                               │
│  ┌──────────────────────────────────────┐                   │
│  │ • 装饰器叠加顺序与属性冲突            │                   │
│  │ • 闭包变量绑定时机 (late/early)      │                   │
│  │ • async/方法/sync 兼容性            │                   │
│  │ • 签名丢失、异常栈吞、循环陷阱排查    │                   │
│  └──────────┬───────────────────────────┘                   │
│             │                                                │
│    ┌────────┼───────────────┬────────────────┐               │
│    ↓        ↓               ↓                ↓               │
│  inspect  traceback       dis              cProfile          │
│  unwrap()  extract_tb()  dis.dis()       Profile()           │
│  signature format_exc()  show_code()     pstats.Stats()     │
│                                                             │
│  Python 调试工具链:                                          │
│  ─────────────────────                                       │
│  • inspect: signature, getsource, unwrap, iscoroutinefunction│
│  • traceback: extract_tb, format_exc, print_exc             │
│  • dis: dis, show_code, code_info                           │
│  • cProfile/pstats: 性能分析                                 │
│  • py-spy: 生产环境非侵入式性能采样                             │
│                                                             │
│  生产者排查层级:                                              │
│  ──────────────                                              │
│  L1: 日志分析(func.__name__、结构体日志)                      │
│  L2: 性能追踪(PerformanceTracker、cProfile)                  │
│  L3: 字节码分析(dis.dis、__code__.co_freevars)              │
│  L4: 调用栈分析(traceback、__wrapped__ 链)                   │
│  L5: 分布式追踪(opentelemetry、Jaeger)                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

本章能力清单

完成本章后,你应该能够:

Part A-C: 边界情况与调试

  • [ ] 识别装饰器叠加顺序导致的属性冲突
  • [ ] 正确处理类装饰器与函数装饰器的叠加顺序
  • [ ] 区分 late-binding 和 early-binding
  • [ ] 编写兼容 async 和方法的装饰器
  • [ ] 使用 __wrapped__inspect 调试签名丢失
  • [ ] 保留异常栈信息(raise ... from e
  • [ ] 使用 inspect_closure_trap() 排查闭包陷阱
  • [ ] 使用 inspect.iscoroutine 排查 await 遗漏
  • [ ] 应用生产级装饰器排查清单

Part D: 生产环境调试实战

  • [ ] 分析生产日志定位 wrapper 问题
  • [ ] 使用 PerformanceTracker 追踪装饰器性能开销
  • [ ] 使用 locate_error_in_chain() 定位复杂装饰器链错误
  • [ ] 使用 named_decorator 为调试添加标识
  • [ ] 使用 combined_decorator 简化装饰器链
  • [ ] 选择合适的调试工具(structlog、cProfile、py-spy)

自检清单

基础问题:

  1. @timer_a @timer_b 的属性冲突如何解决?
  2. 如何获取被多层装饰的原始函数?
  3. 循环变量陷阱的排查工具是什么?
  4. async 装饰器遗漏 await 的症状是什么?
  5. 类装饰器和函数装饰器的正确叠加顺序?

生产环境问题: 6. 生产日志全是 wrapper 时如何排查? 7. 如何追踪装饰器的性能开销? 8. 5层装饰器叠加时如何快速定位错误位置? 9. 如何简化复杂的装饰器链? 10. 生产环境推荐哪些调试工具?

答案

  1. 使用不同属性名,或通过 __wrapped__ 链访问内层
  2. 循环访问 func.__wrapped__ 直到没有该属性
  3. inspect_closure_trap() 检查 cell_contents 是否共享
  4. 返回 coroutine 对象而非结果,inspect.iscoroutine(result) 为 True
  5. 类装饰器在最外层(@ClassDeco @funcDeco def f():
  6. 检查装饰器是否使用 func.__name__ 而非硬编码字符串,使用 analyze_logs() 统计
  7. 使用 PerformanceTracker 收集每次调用耗时,生成统计报告
  8. 使用 locate_error_in_chain() 分析 traceback,区分 decorator 层和 original 层
  9. 将多个功能合并到 combined_decorator,或使用结构化日志记录调用链
  10. structlog(日志)、cProfile(性能)、py-spy(采样)、opentelemetry(分布式追踪)