为啥 Python 有最大递归深度?

Posted

技术标签:

【中文标题】为啥 Python 有最大递归深度?【英文标题】:Why does Python have a maximum recursion depth?为什么 Python 有最大递归深度? 【发布时间】:2015-01-08 12:25:06 【问题描述】:

Python 有最大递归深度,但没有最大迭代深度。为什么递归受限?像迭代一样对待递归,不限制递归调用的次数不是更自然吗?

我只想说这个问题的根源来自尝试实现流(有关流的更多详细信息,请参阅this question)。例如,假设我们要编写一个流来生成自然数:

def stream_accum(s, n): # force the stream to a list of length n
    def loop(s, acc):
        if len(acc) == n:
            return acc
        hd, tl = s()
        return loop(tl, acc + [hd])
    return loop(s, [])


def nats():
    def loop(n):
        return n, lambda: loop(n+1)
    return loop(1)

流的递归定义非常吸引人。但是,我想更好/更 Python 的方法是使用生成器。

【问题讨论】:

“吸引人”的递归解决方案有许多不吸引人的方面。首先,它具有 O(n**2) 行为,因为您不断构建新列表来扩展它们。其次,它过于复杂,因为您可以简单地迭代以产生自然数。这是一个编写 Python 的示例,就好像它是 Scheme 或 Haskell。不同的语言擅长不同的事情。使用迭代。 【参考方案1】:

递归需要call stack 上的空间,它的大小是有限的。使用太多递归级别的代码将给出一个称为stack overflow 的错误(也因some obscure website 而闻名)。 Python 似乎将这个(有点武断)限制在 1000 级左右,但是这个 can be increased 是通过设置 sys.setrecursionlimit 来实现的。

迭代使用类似for-loop 的东西,它是通过增加一些计数器并有条件地将instruction pointer 设置回循环的开头来实现的。这在内存中是恒定的。

【讨论】:

【参考方案2】:

这不是 Python 独有的,与每个调用占用 call stack 上的空间以及堆栈大小受到限制有关。

单独的迭代不会消耗堆栈空间,因此不受此限制。

并非每个递归调用都需要消耗堆栈空间。例如,某些语言可以自动将tail recursion 转换为迭代。但是,CPython 选择不这样做 (Does Python optimize tail recursion?)。

您可以通过调用 sys.setrecursionlimit 来增加 Python 调用堆栈的最大深度。

【讨论】:

栈的大小并没有真正的限制。 (除了堆是有限的。)Python 故意选择限制它。【参考方案3】:

这里实际上存在一些问题。

首先,正如NPE's answer 很好地解释的那样,Python 并没有消除尾调用,所以很多允许无限递归的函数,比如 Scheme,在 Python 中是有限的。

其次,正如 NPE 所解释的那样,无法消除的调用会占用调用堆栈上的空间。而且,即使在使用 TCE 的语言中,也有很多不能像迭代一样对待的递归函数。 (考虑递归调用自身两次的朴素斐波那契函数。)

但为什么调用堆栈首先是有限资源? Python 堆栈帧至少原则上可以在堆上实现并链接在一起(参见Stackless 以获得该原理的存在证明),并且在 64 位内存空间中,有超过 1000 个堆栈帧的空间. (事实上​​,几乎任何现代平台上的 C 堆栈都可以容纳超过 1000 个递归 Python 解释器调用。)

部分原因是历史原因:当您进行递归调用时,普通的 Python 解释器使用固定的 C 堆栈递归调用自身,它最初是为 32 位(甚至 24 或 20 位)平台设计的C 堆栈非常小。

但这本可以改变,Python 3.0 将是改变它的完美场所。那么,他们为什么不呢?因为他们做出了有意识的语言设计决定。在 Pythonic 代码中,递归通常很浅(例如,像 os.walk 这样的代码遍历浅树结构);如果您的函数达到接近 1000 的深度,则它更有可能是一个错误而不是故意的。所以,限制仍然存在。当然,这有点循环——如果他们取消了限制(特别是,如果他们取消了尾调用),更深层次的递归将变得更加惯用。但这就是重点——Guido 不想要一种深度递归是惯用的语言。 (大多数 Python 社区都同意。)

【讨论】:

注意:“你可以通过调用sys.setrecursionlimit来增加Python调用栈的最大深度”

以上是关于为啥 Python 有最大递归深度?的主要内容,如果未能解决你的问题,请参考以下文章

Python:超出最大递归深度

Python_自定义递归的最大深度

Python中的最大递归深度是多少,如何增加它?

Python:调用 Python 对象时超出了最大递归深度

Python递归函数错误:“超出最大递归深度” [重复]

PyQt4 python中的“RuntimeError:调用Python对象时超出最大递归深度”错误