【发布时间】:2012-11-10 05:07:57
【问题描述】:
背景
在玩的时候,我经常写一些简单的递归函数,看起来像:
def f(a,b):
if a>=0 and b>=0:
return min( f(a-1,b) , f(b,a-1) ) # + some cost that depends on a,b
else:
return 0
(例如,在计算加权编辑距离或评估递归定义的数学公式时。)
然后我使用记忆装饰器自动缓存结果。
问题
当我尝试类似 f(200,10) 时,我得到:
RuntimeError: maximum recursion depth exceeded
这符合预期,因为递归实现耗尽了 Python 的堆栈空间/递归限制。
解决方法
我通常通过以下方法之一解决此问题:
- 使用 sys.setrecursionlimit 增加递归限制(仅适用于大约 1000 深度)
- 使用 for 循环为较小的值填充缓存
- 更改函数以将列表用作手动堆栈(通过 append 和 pop 调用)(换句话说,从递归实现转移到迭代实现)
- 使用其他编程语言
但我发现所有这些都很容易出错。
问题
有没有办法编写一个@Bigstack 装饰器来模拟拥有一个非常大的堆栈的效果?
请注意,我的函数通常会进行多次递归函数调用,因此这与尾递归不同 - 我确实希望将每个函数的所有内部状态保存在堆栈中。
我尝试了什么
我一直在考虑使用生成器表达式列表作为我的堆栈。通过探测堆栈帧,我可以确定函数何时被递归调用,然后触发异常以返回装饰器代码。但是,我无法想出一种方法将这些想法粘合在一起,以制造出真正可行的东西。
或者,我可以尝试访问函数的抽象语法树,并尝试将对递归函数的调用转换为 yield 语句,但这似乎走错了方向。
有什么建议吗?
编辑
看起来我确实在滥用 Python,但我一直在考虑的另一种方法是为每个块(例如 500 个堆栈帧)使用不同的线程,然后在每对连续的线程之间插入队列 - 一个队列用于参数,以及另一个返回值队列。 (每个队列最多有一个条目。)我认为这可能由于某种原因不起作用 - 但我可能只有在尝试实现它之后才能弄清楚为什么。
【问题讨论】:
-
是的,Python 中的递归(如 C)通常仅在递归 log(N) 次时才有用,因为它会保持很小。如果您要递归 N 次,则需要迭代。也就是说,这里似乎有一个装饰器黑客:code.activestate.com/recipes/474088
-
如果你真的想要递归,我认为你的手动堆栈方法可能是最健壮和最明确的。魔术是给巫师的,不是程序员的。 :-)
-
相关,请参阅 Guido 关于尾递归消除的“最终话”:Tail Recursion Elimination 和 Final Words on Tail Calls
-
哦,好吧,我想我应该学会停止像 Haskell 一样对待 Python :(
-
@Peter 我可能会在 Haskell 中写那种东西,用导出的函数编译它,然后在 Python 中使用 ctypes 调用它