pytest报recursionerror是因被测或测试代码存在无限递归,如对象自引用、__repr__循环调用、mock误用等,pytest在收集测试或格式化断言差异时深度遍历触发python默认递归限制。

为什么pytest会报 RecursionError: maximum recursion depth exceeded
这不是 pytest 本身的问题,而是被测代码或测试代码里存在意外的无限递归(比如对象自引用、__repr__ 或 __str__ 触发循环调用、mock 时不小心 mock 了自身),pytest 在收集测试、生成失败信息、或打印断言差异时会尝试深度遍历对象——一旦遇到环状结构,就容易撞上 Python 默认的递归限制(通常为 1000 层)。
如何快速定位递归源头?
别急着调大 sys.setrecursionlimit()。先确认是不是测试现场触发的:
- 在报错堆栈最顶部找第一个非 pytest 内部的函数名,比如
my_module.MyClass.__repr__或json.dumps(...)调用点 - 临时在疑似函数开头加
import traceback; traceback.print_stack(limit=3),看谁在反复调用它 - 用
pytest --tb=short -s运行,避免 pytest 自动展开长 traceback 掩盖关键路径 - 如果错误只在断言失败时出现(比如
assert obj1 == obj2),大概率是 pytest 尝试格式化差异时陷入递归,重点检查__eq__、__repr__、__dict__是否有环
常见修复方式与取舍
根本解法是打破递归链,而不是硬扛深度限制:
- 重写
__repr__,跳过自引用字段:def __repr__(self): # 避免 self.parent.children.append(self) 这类结构导致 repr 循环 return f"{self.__class__.__name__}(id={self.id}, parent_id={getattr(self.parent, 'id', None)})" - 测试中用
pytest.approx或显式字段比对,绕过深层对象比较:assert obj.value == expected_value而不是assert obj == expected_obj - mock 时禁用递归属性:用
Mock(spec_set=True)或手动设mock.some_attr = Mock(spec_set=False),防止 mock 自动代理引发循环 - 若必须深比较,改用
deepdiff.DeepDiff并设ignore_order=True, report_repetition=False, verbose_level=0降低遍历强度
什么时候才考虑调高递归限制?
极少情况需要,比如你明确在测试一个合法的、深度达数百层的树形结构解析器,且所有 __repr__ 已安全。此时只应在单个测试函数内临时调整:
import sys <p>def test_deep_tree_parsing(): original_limit = sys.getrecursionlimit() sys.setrecursionlimit(3000) try: result = parse_very_deep_tree() assert result.is_valid finally: sys.setrecursionlimit(original_limit)</p>
注意:全局调用 sys.setrecursionlimit() 会影响整个 pytest 进程,可能导致其他测试莫名失败,而且无法解决根本的环引用问题。
真正难处理的是那些隐式触发递归的场景——比如某个 ORM 模型的 __dict__ 包含 session 对象,session 又引用回模型实例。这种得靠提前剪枝,而不是等报错再救。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











