我有下面的一段代码,失败的错误如下:

RuntimeError:超出最大递归深度

我尝试重写这个代码,以允许尾部递归优化(TCO)。我相信,如果进行了TCO,那么该代码应该是成功的。

def trisum(n, csum):
    if n == 0:
        return csum
    else:
        return trisum(n - 1, csum + n)

print(trisum(1000, 0))

我是否应该得出结论,Python不做任何类型的TCO,或者我只是需要以不同的方式定义它?


当前回答

Guido的话在http://neopythonic.blogspot.co.uk/2009/04/tail-recursion-elimination.html

I recently posted an entry in my Python History blog on the origins of Python's functional features. A side remark about not supporting tail recursion elimination (TRE) immediately sparked several comments about what a pity it is that Python doesn't do this, including links to recent blog entries by others trying to "prove" that TRE can be added to Python easily. So let me defend my position (which is that I don't want TRE in the language). If you want a short answer, it's simply unpythonic. Here's the long answer:

其他回答

基于Guido van Rossum关于该主题的陈述,CPython现在不支持,将来也可能永远不会支持尾部调用优化。

我听说过这样的说法,因为它修改堆栈跟踪的方式,使得调试更加困难。

在Python中,尾调用永远不能优化为跳转。优化是一种保留程序含义的程序转换。尾调用消除并不能保留Python程序的含义。

经常提到的一个问题是,尾部调用消除会改变调用堆栈,而Python允许运行时对堆栈进行内省。但还有一个问题很少被提及。在野外可能有很多这样的代码:

def map_file(path):
    f = open(path, 'rb')
    return mmap.mmap(f.fileno())

对mmap的调用。Mmap在尾部位置。如果它被跳转所取代,那么当前堆栈帧将在控制传递给mmap之前被丢弃。当前堆栈帧包含对文件对象的唯一引用,因此文件对象可以(在CPython中)在mmap被调用之前被释放,这将关闭文件描述符,在mmap看到它之前使其失效。

在最好的情况下,代码将失败并出现异常。在最坏的情况下,文件描述符可能在另一个线程中重用,导致mmap映射错误的文件。因此,这种“优化”对于大量现有的Python代码来说可能是灾难性的。

Python规范保证了这样的问题不会发生,所以可以肯定没有符合规范的实现将return f(args)转换为跳转——除非它有一个复杂的静态分析引擎,可以证明在这种情况下,早期丢弃一个对象不会产生可观察到的结果。


所有这些都不会阻止Python为带有跳转语义的显式尾部调用添加语法,例如

    return from f(args)

这不会破坏没有使用它的代码,而且它可能对自动生成的代码和一些算法有用。GvR不再是BDFL,所以它可能会发生,但我不会屏住呼吸。

除了优化尾部递归,你还可以手动设置递归深度:

import sys
sys.setrecursionlimit(5500000)
print("recursion limit:%d " % (sys.getrecursionlimit()))

Python中没有内置的尾部递归优化。但是,我们可以通过抽象语法树(AST)“重建”函数,消除那里的递归,并用循环替换它。Guido是绝对正确的,这种方法有一些局限性,所以我不推荐使用。

然而,我仍然编写了我自己的优化器版本(而不是作为一个训练示例),您甚至可以尝试它是如何工作的。

通过pip安装此包:

pip install astrologic

现在你可以运行这个示例代码:

from astrologic import no_recursion

counter = 0

@no_recursion
def recursion():
    global counter
    counter += 1
    if counter != 10000000:
        return recursion()
    return counter

print(recursion())

这个解决方案不稳定,您永远不应该在生产中使用它。你可以在github的页面上阅读一些重要的限制(俄语,抱歉)。然而,这个解决方案是相当“真实”的,没有中断代码和其他类似的技巧。

不,而且永远不会,因为吉多·范·罗森更喜欢有正确的回溯

尾递归消去(2009-04-22)

尾部呼叫的最后遗言(2009-04-27)

你可以用这样的转换手动消除递归:

>>> def trisum(n, csum):
...     while True:                     # Change recursion to a while loop
...         if n == 0:
...             return csum
...         n, csum = n - 1, csum + n   # Update parameters instead of tail recursion

>>> trisum(1000,0)
500500