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

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,或者我只是需要以不同的方式定义它?


当前回答

尝试实验macropy TCO实现的大小。

其他回答

在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,所以它可能会发生,但我不会屏住呼吸。

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:

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

尾递归消去(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

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

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

尝试实验macropy TCO实现的大小。