Python递归限制与堆栈大小有什么区别?

12 浏览
0 Comments

Python递归限制与堆栈大小有什么区别?

我理解在递归中,每次递归调用都会在堆栈上堆叠;如果堆栈限制超过了,就会导致堆栈溢出。\n那为什么Python的sys.getrecursionlimit()返回一个数字,即递归调用的最大深度?\n难道它不取决于我在递归函数中做了什么吗?或者它是否在其他地方保存变量,而不是在堆栈上?它是如何工作的?

0
0 Comments

Python递归限制与堆栈大小的问题

Python递归限制与堆栈大小的问题主要是因为Python解释器的堆栈实际上不是一个将所有帧连接在一起的巨大数组,而是一个帧的链表。在C语言的概念中,这个说法可能会让人产生误解。一个变量在C中是一个有类型的内存位置。当你写下int lst[100];时,这会在堆栈上分配400字节的内存,并将其命名为lst。而在Python中,一个变量只是一个值的名称(在某个命名空间中)。内存位置(和类型)是值的属性,不是变量的属性,它们总是存在于堆中。变量只是对它们的引用。因此,当你写下lst = [0]*100时,变量lst只占用8字节(指针)的内存,而列表对象则占用864字节的堆内存。

递归错误限制是为了防止大多数Python代码在达到1000层深度时耗费大量时间来分配大量Python帧,然后在内存错误或堆栈溢出段错误之前失败。所以在分配所有那些内存并消耗所有那些CPU之前,最好在那之前停止你。更重要的是,正如tdelaney在评论中指出的那样,在Python中从这两种情况中恢复是非常困难的,但是从递归错误中恢复相当简单;它会为您解开递归并将您置于可预测的状态。

但是,这个经验法则并不适用于每个程序,只适用于大多数程序。所以,如果你知道你的算法可能会在没有任何问题的情况下进入几千个帧,Python允许你将限制增加到比如10000而不是1000。

需要注意的是,Python解释器实际上确实经常在C堆栈上链接调用,但是记住每次在Python中进行递归时都会分配一个新的帧对象(以及帧分配的其他东西)是非常有用的。尤其是因为Python在Python级别上从不进行尾调用消除,即使解释器在eval循环中实际上进行了尾调用消除。

总结一下,Python递归限制与堆栈大小的问题出现的原因是为了避免内存错误和堆栈溢出段错误。解决方法是在达到递归限制之前停止递归。如果知道算法可能会超过限制而不会出现问题,则可以将限制增加到更高的值。

1. 这是过于简化的解释,因为(至少在CPython中)解释器实际上经常在C堆栈上链接调用,但是记住每次在Python中进行递归时都会分配一个新的帧对象(以及帧分配的其他东西)是非常有用的。尤其是因为Python在Python级别上从不进行尾调用消除,即使解释器在eval循环中实际上进行了尾调用消除。

2. 严格来说,在Python中,所有变量都存储在命名空间中,即从名称到值的引用的映射。但是CPython通过存储指针数组并让编译器将局部引用转换为数组查找而进行了局部变量的优化,而不是进行映射查找。

3. 当然,这个“某处”是未指定的,Python是垃圾回收的,无论是使用CPython中的自动引用计数加循环检测器,还是使用底层JVM中使用的内容。但是在CPython中,还有一个定义好的C API,对象是指向结构体的C指针,你可以使用id函数查看这个指针的值。

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

0