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递归限制与堆栈大小?的主要内容,如果未能解决你的问题,请参考以下文章