本文目录导读:
- 核心底层参数:
Py_RecursionLimit - 它如何控制递归 “深度”?
- 不改变但受限于的底层参数:C 栈大小(OS 级)
- 一个容易被忽略的点:
sys.setrecursionlimit并没有设置 “虚拟内存” 或 “栈预留空间” - 总结与建议
- 补充:如何查看当前递归深度?
sys.setrecursionlimit 主要改变的是 Python 解释器(CPython)内部的递归深度上限,但它并不直接改变 C 语言层面的栈空间大小或操作系统的栈限制,底层具体影响的参数和机制如下:
核心底层参数:Py_RecursionLimit
- 在 CPython 源码(
Python/ceval.c和Include/cpython/ceval.h)中,有一个全局变量int Py_RecursionLimit。 sys.setrecursionlimit(n)本质上就是执行:Py_RecursionLimit = n;
- 这个值默认是 1000(Python 3.5+,早期版本常用 1000)。
它如何控制递归 “深度”?
CPython 在执行 Python 函数调用时,会在 执行栈(call stack) 上增加一层帧(frame),每次进入一个函数,解释器内部都会检查当前递归深度:
if (tstate->recursion_depth > Py_RecursionLimit) {
PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded");
// 触发异常
}
- 递归深度由
tstate->recursion_depth记录,它是一个int,每次函数调用 +1,返回时 -1。 setrecursionlimit改变的正是这个if比较的阈值。
不改变但受限于的底层参数:C 栈大小(OS 级)
- Python 的递归最终会消耗 C 栈空间(因为 Python 函数调用最终由 C 函数
PyEval_EvalFrameDefault等递归驱动)。 - 如果设置过大的
recursionlimit(如 100000),虽然 Python 层面不报错,但C 栈会溢出,导致 Segmentation Fault(段错误)或 Stack Overflow(栈溢出),而不是 Python 异常。 - 这个限制由 操作系统线程栈大小(
ulimit -s在 Linux/macOS)或 可执行文件链接时的栈大小 决定。
一个容易被忽略的点:sys.setrecursionlimit 并没有设置 “虚拟内存” 或 “栈预留空间”
- 它不是修改
setrlimit(RLIMIT_STACK)或mmap的栈增长参数。 - 它只是一个纯软件计数器阈值,完全在 Python 层面,不涉及任何操作系统系统调用。
总结与建议
| 参数层次 | 是否被setrecursionlimit改变 |
典型默认值 | |
|---|---|---|---|
| Python 层面 | Py_RecursionLimit |
是 | 1000 |
| 解释器 C 层面 | C 函数调用栈 | 否 | 取决于 OS 栈大小 |
| 操作系统层面 | 线程栈大小(RLIMIT_STACK) | 否 | 8 MB(Linux 默认) |
所以:
- 如果你把
recursionlimit设得太大(100000),而你的递归函数每次调用消耗大量局部变量或深层嵌套,则真实危险是 C 栈溢出 →Segmentation Fault。 - 唯一安全的方式是同时增大操作系统的线程栈大小(例如通过
threading.stack_size()或ulimit -s),但这与setrecursionlimit无关。
补充:如何查看当前递归深度?
import sys # 查看当前递归深度(需要小心,调用该函数自身也会增加一层深度) print(sys.getrecursionlimit()) # 返回当前递归限制
如果你需要底层源码细节,可以参考 CPython 的 Python/ceval.c 中的 _PyEval_EvalFrameDefault 函数里:
if (tstate->recursion_depth++ > Py_RecursionLimit) {
// raise RecursionError
} 标签: 递归深度限制