RecursionError常因隐式或未收敛递归触发,如__getattr__循环调用、__repr__含循环引用;定位需截断堆栈或加跟踪打印;转迭代须显式管理状态;仅在深度可控且无逻辑缺陷时谨慎调高限制。

为什么RecursionError: maximum recursion depth exceeded总在不该出现的地方报?
Python 默认递归深度限制是 1000,这个值不是凭空定的——它对应 CPython 解释器栈帧的内存安全边界。但问题常出在「隐式递归」或「未收敛递归」上,比如误把 __getattr__ 当成普通方法用、装饰器没处理好 self 引用、或者用递归实现树遍历时忘了剪枝。
关键判断:如果错误出现在你没写明显递归调用的地方(比如 json.dumps()、pprint.pprint()、甚至 str(obj)),大概率是对象的魔法方法触发了循环代理或无限委托。
- 检查是否重写了
__getattr__却在内部访问了不存在的属性(又触发自身) - 确认自定义类的
__repr__或__str__没有间接引用自身(如打印self.__dict__时含循环引用) - 用
sys.getrecursionlimit()看当前限制,但别盲目调高——治标不治本
怎么快速定位哪一层递归卡住了?
靠 traceback.print_exc() 只能看到末尾几百行重复堆栈,真正有用的是捕获并截断调用链。推荐在疑似入口加一层轻量级跟踪:
import sys
import traceback
<p>def safe_call(func, *args, *<em>kwargs):
try:
return func(</em>args, **kwargs)
except RecursionError as e:</p><h1>打印最近 20 层调用,避免刷屏</h1><pre class="brush:php;toolbar:false;"> tb = traceback.extract_tb(sys.exc_info()[2])[-20:]
print("Recursion stuck in:")
for frame in tb:
print(f" {frame.filename}:{frame.lineno} in {frame.name}")
raise
更直接的办法是启用 Python 内置跟踪:启动时加 -X tracemalloc 或临时插入 sys.settrace(),但要注意性能损耗大,仅用于调试。
- 优先用
python -v启动看模块导入是否形成循环依赖(常见于包内子模块相互 import) - 对类方法,加一句
print(f"{self.__class__.__name__}.{sys._getframe().f_code.co_name}")快速观察调用流向 - 注意
functools.lru_cache装饰器可能掩盖递归问题——缓存键若含可变对象,会导致意外重复调用
递归改迭代时,哪些结构最容易翻车?
把递归转成栈模拟迭代,核心是「显式维护状态」。最常漏掉的是:没保存中间变量、搞混入栈顺序、或忘记处理回溯逻辑。比如 DFS 遍历二叉树,递归写法天然带「返回时清理」语义,而迭代需手动管理节点和访问标记。
一个安全模板:
stack = [(root, "enter")] # 状态标记比单纯放 node 更可靠
while stack:
node, phase = stack.pop()
if node is None:
continue
if phase == "enter":
# 前序逻辑
stack.append((node, "exit"))
stack.append((node.left, "enter"))
stack.append((node.right, "enter"))
else:
# 后序逻辑(相当于递归函数 return 后的代码)
pass
- 避免用
list.append()+list.pop(0)模拟队列——这是 O(n) 操作,改用collections.deque - 递归中「多个分支并行」(如背包问题)转迭代时,务必确保每个分支的状态完整入栈,不能只压 node 而丢掉已选物品列表
- Python 的
yield from在生成器递归中很省事,但一旦要加缓存或中断逻辑,就得拆成显式栈
什么时候真该调高 sys.setrecursionlimit()?
极少情况需要调高。典型合理场景只有:处理已知深度可控的大规模数据结构(如平衡二叉树高度约 log₂(n),n=10⁶ 时深度仅 ~20),且确认无逻辑缺陷。
但必须同步做两件事:
- 调高前先用
resource.setrlimit(resource.RLIMIT_STACK, ...)(Linux/macOS)预留足够系统栈空间,否则 Python 会 segfault - 永远配合
try/except RecursionError做兜底,防止用户输入意外触发深层嵌套(如解析恶意构造的 JSON) - 在多线程环境里,
sys.setrecursionlimit()是进程级的,但每个线程有自己的栈大小,需单独设置threading.stack_size()
最常被忽略的一点:C 扩展模块(如 numpy、lxml)的回调函数如果 Python 层递归过深,错误可能表现为段错误而非 RecursionError,这时得用 faulthandler.enable() 捕获原始信号。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











