Python优化尾部递归吗?
我有以下一段代码失败,错误如下: 运行时错误:超过最大递归深度 我试图重写它以允许尾部递归优化(TCO)。我相信如果发生了TCO,这段代码应该是成功的Python优化尾部递归吗?,python,recursion,stack,stack-overflow,tail-recursion,Python,Recursion,Stack,Stack Overflow,Tail Recursion,我有以下一段代码失败,错误如下: 运行时错误:超过最大递归深度 我试图重写它以允许尾部递归优化(TCO)。我相信如果发生了TCO,这段代码应该是成功的 def trisum(n, csum): if n == 0: return csum else: return trisum(n - 1, csum + n) print(trisum(1000, 0)) 我是否应该得出这样的结论:Python不具备任何类型的TCO,还是我只需要对其进行不同的
def trisum(n, csum):
if n == 0:
return csum
else:
return trisum(n - 1, csum + n)
print(trisum(1000, 0))
我是否应该得出这样的结论:Python不具备任何类型的TCO,还是我只需要对其进行不同的定义?不支持,也可能永远不会支持基于该主题的尾部调用优化
我听过这样的观点,即由于它如何修改堆栈跟踪,调试变得更加困难。不,而且它永远不会,因为它更喜欢能够进行正确的跟踪: (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
Guido这个词在
我最近在我的Python历史博客上发表了一篇关于
Python的功能特性。关于不支持尾部的旁注
递归消除(TRE)立即引发了关于
可惜的是Python没有做到这一点,包括到
其他人最近的博客文章试图“证明”可以添加TRE
要轻松地使用Python。所以让我来捍卫我的立场(我不这么认为)
想要英语中的TRE)。如果你想要一个简短的答案,那很简单
非音速的。答案很长:
我发布了一个执行尾部调用优化(处理尾部递归和延续传递样式)的模块: Python中尾部递归的优化 经常有人声称尾部递归不适合Pythonic的编码方式,人们不应该关心如何将其嵌入到循环中。我不想和你争论 这一观点;然而,有时我喜欢尝试或实施新的想法 作为尾部递归函数,而不是由于各种原因使用循环(重点是 想法,而不是在过程中,有二十个短功能在我的屏幕上在同一时间 时间,而不仅仅是三个“Pythonic”函数,在交互式会话中工作,而不是编辑我的代码,等等) 在Python中优化尾部递归实际上相当容易。虽然据说这是不可能的 或者非常棘手,我认为它可以通过优雅、简短和通用的解决方案来实现;我甚至 认为这些解决方案中的大多数都不使用Python特性,而应该使用其他特性。 干净的lambda表达式与非常标准的循环一起工作,可以实现快速、高效和高效的循环 用于实现尾部递归优化的完全可用的工具 为了个人方便,我编写了一个实现这种优化的小模块 通过两种不同的方式。我想在这里讨论我的两个主要功能 干净的方法:修改Y组合符 这是众所周知的;它允许以递归方式使用lambda函数 但它本身不允许在循环中嵌入递归调用。兰姆达 光靠微积分是做不到这一点的。然而,在Y组合中有一点变化 可以保护要实际计算的递归调用。因此,评估可能会推迟 下面是Y组合器的著名表达式:
lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args)))
只要稍作改动,我就能得到:
lambda f: (lambda x: x(x))(lambda y: f(lambda *args: lambda: y(y)(*args)))
函数f现在返回一个执行
非常相同的调用,但由于它返回了它,因此可以稍后从外部进行评估
我的代码是:
def bet(func):
b = (lambda f: (lambda x: x(x))(lambda y:
f(lambda *args: lambda: y(y)(*args))))(func)
def wrapper(*args):
out = b(*args)
while callable(out):
out = out()
return out
return wrapper
该功能可按以下方式使用:;下面是两个带有尾部递归的示例
阶乘和斐波那契的版本:
>>> from recursion import *
>>> fac = bet( lambda f: lambda n, a: a if not n else f(n-1,a*n) )
>>> fac(5,1)
120
>>> fibo = bet( lambda f: lambda n,p,q: p if not n else f(n-1,q,p+q) )
>>> fibo(10,0,1)
55
显然,递归深度不再是问题:
>>> bet( lambda f: lambda n: 42 if not n else f(n-1) )(50000)
42
这当然是函数的唯一真正目的
只有一件事不能用这个优化来完成:它不能用在
尾部递归函数求值到另一个函数(这来自事实
所有可调用的返回对象都作为进一步的递归调用进行处理
没有区别)。因为我通常不需要这样的功能,我很高兴
使用上面的代码。然而,为了提供一个更通用的模块,我认为
为了找到解决此问题的一些解决方法(请参阅下一节),请再做一点
关于这个过程的速度(这不是真正的问题),它发生了
很好,;尾部递归函数的计算速度甚至比使用
以下代码使用更简单的表达式:
def bet1(func):
def wrapper(*args):
out = func(lambda *x: lambda: x)(*args)
while callable(out):
out = func(lambda *x: lambda: x)(*out())
return out
return wrapper
我认为计算一个表达式,即使很复杂,也比
计算几个简单表达式,第二个版本就是这样。
我没有在我的模块中保留这个新函数,而且我看不出在什么情况下它会出现
可以使用,而不是“官方”的
带例外的连续传递样式
这里有一个更一般的函数;它能够处理所有尾部递归函数,
包括那些返回其他函数的函数。递归调用是从
通过使用异常返回的其他值。此解决方案比
前一个;一个更快的代码可能是通过使用一些特殊的
在主循环中检测到作为“标志”的值,但我不喜欢
使用特殊值或内部关键字。有一些有趣的解释
使用异常的方法:如果Python不喜欢尾部递归调用,则会出现异常
应该在发生尾部递归调用时引发,并且python方法将
捕捉异常以找到干净的解决方案,这实际上是什么
发生在这里
class _RecursiveCall(Exception):
def __init__(self, *args):
self.args = args
def _recursiveCallback(*args):
raise _RecursiveCall(*args)
def bet0(func):
def wrapper(*args):
while True:
try:
return func(_recursiveCallback)(*args)
except _RecursiveCall as e:
args = e.args
return wrapper
现在所有功能都可以使用了。在下面的示例中,f(n)
计算为
n的任何正值的单位函数:
>>> f = bet0( lambda f: lambda n: (lambda x: x) if not n else f(n-1) )
>>> f(5)(42)
42
当然,有人可能会争辩说,例外情况并非有意用于
重定向解释器(作为一种goto
语句,或者更确切地说是一种
我必须承认这一点。但是,再一次,
我觉得使用try
的想法很有趣,其中一行是return
import sys
sys.setrecursionlimit(5500000)
print("recursion limit:%d " % (sys.getrecursionlimit()))
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())