断点设在装饰器调用行(如@decorator)无法进入wrapper函数,因该行仅绑定不执行;需将断点设在装饰器内部wrapper函数体中才能调试其逻辑,f11/f10行为受装饰器实现、justmycode设置及闭包作用域影响。

断点设在装饰器调用行,F11 通常进不去
装饰器本质是函数包装,@decorator 这一行本身不执行逻辑,只做绑定。你点 F11 想“跳入”,调试器会直接跳过它,停在被装饰函数的第一行(比如 def my_func(): 的下一行),而不是装饰器内部的 wrapper 函数里。
真正想查装饰器行为,得把断点打在装饰器定义内部——尤其是 wrapper 函数体里,或者 return wrapper 之后实际触发的位置(比如函数被调用时)。
- 常见错误:在
@log_time那行设断点 → 断点无效或自动跳过 - 正确做法:打开装饰器模块,在
def wrapper(*args, **kwargs):下一行设断点 - 如果装饰器是第三方库(如
@lru_cache),无法编辑源码,就只能在被装饰函数入口处设断点,再用Step Into看是否能跟进去——多数内置装饰器不支持单步进入,因为它们是 C 实现
用 F10 单步跳过时,装饰器的 wrapper 执行会被“吞掉”
F10 是按“逻辑行”跳过,不是按“物理行”。装饰器调用后那一行(比如 result = my_func())执行时,my_func 实际是 wrapper,但 F10 不会展开它,直接执行完并停在下一行。你完全看不到 wrapper 里干了什么。
这时候必须切回 F11,但前提是断点已落在 wrapper 内部,否则 F11 依然无效。
- 验证方法:在
wrapper开头加一句print("in wrapper"),运行看是否输出;没输出说明根本没进 wrapper,可能是装饰器没生效或被 @wraps 覆盖了栈帧 - 装饰器用了
@functools.wraps(func)后,调试器看到的函数名仍是原函数,但实际执行的是 wrapper —— 这会让 F11 行为更难预测 - 多层装饰器(
@dec1套@dec2)时,F11 只会进最外层 wrapper,想进第二层得提前在那层设好断点
launch.json 中必须启用 Just My Code 才能稳定进入自定义装饰器
VSCode 默认开启 "justMyCode": true(在 launch.json 里),这会让调试器跳过标准库和第三方包代码,但它也会误判某些装饰器为“非我的代码”,尤其当装饰器定义在 site-packages 里时。
如果你写的是自己的装饰器模块(比如 utils/decorators.py),确保它在工作区根目录下,并且 launch.json 中 "justMyCode" 设为 true(默认值即可)。设成 false 反而容易卡在 functools.py 里出不来。
- 检查路径:断点所在的装饰器文件,必须能被 VSCode 解析为 workspace 内部路径,不能是 symlink 或压缩包内路径
- 如果装饰器来自 pip 安装的包,又想单步进它的
wrapper,唯一办法是手动把包源码拷到项目里,改import路径,否则调试器不认 - PyLance 和 Pylint 不影响调试,但它们的类型提示可能误导你认为某行可断点 —— 实际上装饰器里的闭包变量在调试器里可能显示为
<undefined></undefined>
Watch 表达式查不到 wrapper 里的局部变量
装饰器的 wrapper 是闭包,它的局部变量(比如 start_time、args)在调试器的 “Variables” 面板里不会直接列出,尤其当它们被 nonlocal 或嵌套作用域捕获时。
这时得靠 Debug Console 手动输入表达式,但要注意作用域限制:你只能访问当前暂停位置可见的变量。
- 在
wrapper函数内设断点后,在 Debug Console 输入locals(),能看到所有闭包变量 - 输入
args或kwargs通常有效;但start_time这类计算中间量,可能因优化被丢弃,除非你在那行显式赋值并暂停 - 别依赖 “Call Stack” 面板里显示的函数名判断当前在哪——它常显示被装饰函数名,实际执行的是 wrapper,这点极易误判
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











