news 2026/9/15 21:59:47

pytest 8.0.2 版本解析:一个补丁修复如何让 Cython 模块的异常回溯不再显示 “???“

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
pytest 8.0.2 版本解析:一个补丁修复如何让 Cython 模块的异常回溯不再显示 “???“

pytest 8.0.2 版本解析:一个补丁修复如何让 Cython 模块的异常回溯不再显示 "???"

【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest

导读

本文基于 pytest 官方发布的 8.0.2 补丁版本公告,深入解析该版本的核心修复:当代码以"相对于项目根目录而非当前工作目录"的路径编译(典型如 Cython 编译模块)且 pytest 从子目录运行时,异常回溯不再显示无意义的???,而是正确还原源码行。你将了解到该补丁的升级方式、底层findsource()源码查找机制、sys.path兜底策略以及对应的回归测试,可直接在当前仓库中逐行对照验证。

一、发布背景:一个 "drop-in replacement" 的补丁版本

在 doc/en/announce/release-8.0.2.rst 公告中,pytest 官方明确了 8.0.2 的定位:

This is a bug-fix release, being a drop-in replacement.

也就是说,8.0.2 是 8.0 系列的一个纯缺陷修复版本,不包含破坏性变更,也不改变既有 API 与行为,因此可以在任何 8.0.x 环境上无痛替换。该版本由贡献者 Ran Benita 完成,已发布至 PyPI。

升级方式

公告给出的升级命令非常直接:

pip install --upgrade pytest

对于使用隔离环境(如 venv/conda)或依赖锁定工具(pip-tools、poetry、uv 等)的项目,同样只需将版本约束更新到pytest>=8.0.2,<8.1范围即可。由于是 drop-in replacement,升级后无需修改任何测试代码或配置文件(如 pyproject.toml 中的[tool.pytest.ini_options]、根目录的 tox.ini 等)。

二、核心修复:回溯中的 "???" 从何而来

本次发布的唯一实质变更记录在 changelog/1139.bugfix.rst:

Fixed tracebacks showing???instead of source lines for code compiled with a filename relative to a directory other than the current working directory -- e.g. compiled Cython modules, when running pytest from a subdirectory of the project root. The source lookup now falls back to searchingsys.pathfor such filenames, as the standard :mod:tracebackmodule does.

问题场景

要理解这个问题,需要先认识 Python 代码对象(code object)中的co_filename字段。它记录了代码来源文件的路径,但这个路径是编译时确定的,并不保证是绝对路径。典型的触发场景包括:

  • Cython 编译模块:Cython 在编译.pyx/.py时,可能将co_filename记录为相对于项目根目录的相对路径;
  • 任何以相对路径compile()出的代码对象(如动态生成代码、某些打包工具产物)。

当 pytest 恰好从项目根目录的子目录启动时,当前工作目录(cwd)与co_filename所相对的那个目录不再一致。此时若断言失败或抛出异常,pytest 需要把源码行渲染到回溯信息里,却按 cwd 找不到对应文件,最终只能退化为显示???——用户无法从回溯中看到出错的代码内容,排查成本大增。

标准库的解法

Python 标准库traceback模块面对同样的情形时,并不直接以 cwd 为基准查找文件,而是借助linecachelinecache.getlines(filename)在本地文件查找失败后,会遍历sys.path,对每个路径项尝试拼接裸文件名进行匹配。也就是说,标准库早已具备"在sys.path中兜底搜索"的能力,而 pytest 此前的源码查找逻辑没有对齐这一行为。

三、源码级解析:findsource()的兜底链

修复的落点位于 src/_pytest/_code/source.py 中的findsource()函数。该函数是 pytest 从代码对象还原源码文件的核心入口,其修复后逻辑如下:

def findsource(obj) -> tuple[Source | None, int]: try: sourcelines, lineno = inspect.findsource(obj) except Exception: # inspect.findsource() fails for code objects whose co_filename is # relative to a directory other than the cwd, e.g. compiled Cython # modules when running pytest from a subdirectory (#1139). # Fall back to linecache, which also searches sys.path for bare # filenames, as the standard traceback module does. code = ( obj if isinstance(obj, types.CodeType) else getattr(obj, "__code__", None) ) if code is None: return None, -1 sourcelines = linecache.getlines(code.co_filename) if not sourcelines: return None, -1 lineno = code.co_firstlineno - 1 source = Source() source.lines = [line.rstrip() for line in sourcelines] source.raw_lines = sourcelines return source, lineno

三段式工作流

  1. 主路径:优先调用inspect.findsource(obj),它在绝大多数常规情况下(源码文件路径可从 cwd 解析)直接命中;
  2. 异常兜底:一旦抛出异常(正是co_filename相对目录与 cwd 不一致的场景),不再放弃,而是取出代码对象(obj自身若是CodeType则直接用,否则取__code__属性),改调linecache.getlines(code.co_filename)linecache内部会走sys.path搜索裸文件名,与标准库traceback行为一致;
  3. 失败判定:若仍取不到任何行(not sourcelines),返回(None, -1),由上层决定如何渲染。

值得注意的是,第 139-141 行的防御处理:当对象拿不到__code__(例如纯内置对象)时返回(None, -1),避免让异常进一步扩散到回溯渲染流程中。

在回溯渲染链路中的位置

findsource()并非孤立函数,它被 src/_pytest/_code/code.py 中的多个关键路径依赖:

  • Code.fullsource属性(code.py)直接调用findsource(self.raw),为ExceptionInfo/TracebackEntry提供整份源码文件
  • getfslineno()(code.py)在计算"文件 + 行号"定位信息时同样调用findsource求行号。

因此,只要findsource()能正确解析源码,最终--tb=long/--tb=short等回溯模式就能展示真实的源码行与^定位符,而不再退化为???。可以说,这一个函数是"回溯信息可读性"的命脉。

四、回归测试:如何验证这个修复

仓库在 testing/code/test_source.py 中为该修复新增了专项回归测试test_findsource_filename_relative_to_syspath_entry

def test_findsource_filename_relative_to_syspath_entry( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: """findsource() falls back to searching sys.path for code objects whose co_filename is relative to a directory other than the cwd, like the standard traceback module does (#1139). This happens e.g. with compiled Cython modules, whose code objects carry paths relative to the project root, when pytest is run from a subdirectory. """ from _pytest._code.source import findsource filename = "findsource_syspath_demo.py" lines = ["def f():\n", " return 1\n"] (tmp_path / filename).write_text("".join(lines), encoding="utf-8") co = compile("".join(lines), filename, "exec") d: dict[str, Any] = {} eval(co, d) empty = tmp_path / "empty" empty.mkdir() monkeypatch.chdir(empty) monkeypatch.syspath_prepend(str(tmp_path)) src, lineno = findsource(d["f"].__code__) assert src is not None assert lineno == 0 assert src[lineno] == "def f():"

该测试精准复现了缺陷条件:

  1. compile()纯文件名"findsource_syspath_demo.py",即相对路径)编译一段代码,模拟 Cython 编译产物的co_filename形态;
  2. 通过monkeypatch.chdir切换到另一个空目录,模拟"从项目子目录运行 pytest";
  3. 通过monkeypatch.syspath_prepend把源文件所在目录塞入sys.path
  4. 最终断言findsource()能通过sys.path兜底找到源码,且行号与内容精确匹配。

同文件中的 test_findsource_fallback 与 test_findsource 则覆盖了普通函数对象与通过linecache.cache预置内存源码的路径,共同构成对该查找逻辑的完整测试矩阵。

五、升级影响与验证建议

由于 8.0.2 是 bug-fix 版,升级后的行为差异几乎只体现在"曾经显示???的场景现在能正常显示源码",因此验证手段也很直接:

  1. 准备一个 Cython 模块(或任意co_filename为相对路径的代码对象);
  2. 在其中故意触发异常或失败的断言;
  3. 分别在项目根目录根目录的某个子目录下运行 pytest(如python -m pytestpytest);
  4. 观察失败回溯:8.0.2 之前子目录运行会显示???,升级后应显示真实源码行与行号。

完整的版本变更可查阅仓库根目录 CHANGELOG.rst,该文件聚合了每次发布的全部变更条目,8.0.2 对应的条目即由 changelog/1139.bugfix.rst 生成。

结语

pytest 8.0.2 虽是一个仅有单条修复的小版本,却体现了测试框架在"异常可读性"上的较真:让回溯信息在冷门的相对路径编译场景下也能忠实还原代码现场。透过 src/_pytest/_code/source.py 中findsource()的三段式兜底设计与 testing/code/test_source.py 中的精确回归测试,我们可以看到一条成熟的修复路径——先对齐标准库行为,再用最小化的测试复现缺陷条件,最终在不破坏任何既有 API 的前提下以 drop-in replacement 的形式发布。这正是补丁版本应有的姿态:小、稳、可预期。

【免费下载链接】pytestThe pytest framework makes it easy to write small tests, yet scales to support complex functional testing项目地址: https://gitcode.com/GitHub_Trending/py/pytest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 21:56:20

Qt/C++贪吃蛇游戏开发:从游戏循环到碰撞检测的工程实践

简介&#xff1a;基于Qt与C的贪吃蛇游戏毕业设计完整方案&#xff0c;适合计算机相关专业学生完成课程设计或毕业设计时参考。压缩包内含可编译运行的源码、毕业设计论文、任务书以及答辩PPT&#xff0c;从需求分析、总体设计到编码实现均有对应文档支撑&#xff1b;论文中对技…

作者头像 李华
网站建设 2026/9/15 21:55:30

拒绝丑模板!汕头建站模板搭建的3个最佳实践避坑指南

拒绝丑模板!汕头建站模板搭建的3个最佳实践避坑指南 别再盯着那些一眼假、加载慢、还改不动的免费模板发愁了。很多老板觉得建站就是下个模板换个Logo,结果上线后客户直摇头,说像十年前网页,更别提转化了。这就是典型的“模板网站太丑不够用”。…

作者头像 李华
网站建设 2026/9/15 21:50:52

Linux跑Windows应用中文乱码?WinBoat字体修复指南

Linux跑Windows应用中文乱码&#xff1f;WinBoat字体修复指南 【免费下载链接】winboat Run Windows apps on &#x1f427; Linux with ✨ seamless integration 项目地址: https://gitcode.com/GitHub_Trending/wi/winboat 在 WinBoat 里打开记事本输入中文&#xff0…

作者头像 李华
网站建设 2026/9/15 21:49:45

LIN总线UDS OTA升级实战:调度表与原子性设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 21:49:16

跨境电商哪个平台好?这份建站避坑指南帮你省10万

跨境电商哪个平台好?这份建站避坑指南帮你省10万 很多老板一上来就问:“跨境电商哪个平台好?”但往往还没选对平台,网站就崩了。最让人头大的,莫过于备案流程一头雾水,域名解析配置错误,或者服务器带宽选小了导致访问卡顿。这不仅是技术问题,更是生意问题。今天不谈虚的,直接给出一套经过实战检验的 避坑指南…

作者头像 李华