Python递归限制与堆栈大小?

Posted

技术标签:

【中文标题】Python递归限制与堆栈大小?【英文标题】:Python recursion limit vs stack size? 【发布时间】:2018-10-16 00:23:32 【问题描述】:

我了解在递归中每个递归调用如何堆叠在堆栈上;如果超出堆栈限制,则会出现堆栈溢出。 为什么 Python 的 sys.getrecursionlimit() 返回一个数字;递归调用的最大深度?

这不取决于我在该递归函数中所做的事情吗?或者它是否以某种方式将变量保存在堆栈以外的其他地方?它是如何工作的?

【问题讨论】:

极不可能从进程级堆栈溢出或内存不足的情况中恢复。 您可能会觉得这很有帮助:***.com/questions/23848391/… @tdelaney 是的,这可能是最重要的一点:您几乎总是可以从RecursionError 中恢复,但由于将递归深度增加到荒谬的事情,您很少能从MemoryError 中恢复… 递归“限制”并不是真正的事情。例如和解释见this answer。 【参考方案1】:

如果过于简单,考虑这一点的简单方法是,Python 堆栈实际上并不是一个包含所有帧的巨型数组,而是一个帧的链表。1 但即使这样也可以如果您在考虑 C 术语,则可能会产生误导。你似乎是:

或者它是否以某种方式将变量保存在堆栈以外的其他地方?

确实——在 CPython 中,局部变量2 存储在堆分配框架对象的数组中——但这通常不是相关问题。

在 C 中,变量是一个类型化的内存位置。当你写 int lst[100]; 时,它会在堆栈上分配 400 个字节并将其命名为 lst

在 Python 中,变量只是一个值的名称(在某个命名空间中)。内存位置(和类型)是值的属性,而不是变量的属性,它们总是存在于堆中的某个位置。3 变量只是对它们的引用。所以,如果你写lst = [0]*100,那么locals数组中的变量(指针)只有8个字节,堆上的列表对象只有864个字节。4


RecursionError 存在限制,因为大多数深度为 1000 的 Python 代码可能只需要很长时间才能分配一大堆 Python 帧,然后在任一MemoryError 或堆栈溢出段错误,因此最好在分配所有内存并烧毁所有 CPU 之前阻止您。

更重要的是,正如 tdelaney 在评论中指出的那样,在 Python 中从其中任何一种情况中恢复都非常困难——但从RecursionError 中恢复非常简单;它为您将堆栈展开到递归的顶部,并使您处于可预测的状态。

但这条经验法则并不适用于每个程序,仅适用于大多数程序——所以如果你知道你有一个算法可以深入几千帧而没有任何问题,Python 让您将限制增加到 10000 而不是 1000。


1。这被过度简化了,因为(至少在 CPython 中)解释器 通常实际上将 C 堆栈上的调用链接起来——但记住有一个新的框架对象(以及框架分配的其他东西)仍然很有用) 每次在 Python 中递归时都会分配堆,无论解释器是否递归。 (特别是因为 Python 被定义为在 Python 级别从不进行尾调用消除,即使解释器实际上在 eval 循环中这样做。)

2。从技术上讲,在 Python 中,所有变量都存储在命名空间中,这是从名称到引用到值的映射。但是 CPython 通过存储指针数组来优化局部变量,然后让编译器将局部引用转换为数组查找而不是映射查找。

3。当然,“某处”是未指定的——Python 是垃圾收集的,无论是在 CPython 中使用自动引用计数和循环检测器,还是在 Jython 中使用任何底层 JVM 都可以。但在 CPython 中,还有一个定义好的 C API,其中对象是指向结构的 C 指针——您可以使用 id 函数查看该指针的值。

4.此外,这 864 个字节主要只是一个包含 100 个指向单个不可变 0 对象的指针的列表,这与 C 不同,其中有 100 个单独的可变 int 插槽,它们都具有值 0

【讨论】:

以上是关于Python递归限制与堆栈大小?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个递归函数超过调用堆栈大小?

如何获取 Python 解释器堆栈的当前深度?

python 3中使用堆栈的递归与迭代

如何增加python中的堆栈大小

递归与手动堆栈 - 在哪种情况下首选哪个?

如何增加 Rust 库可用的堆栈大小?