作为实验的一部分,我在python中编写了一个verilog(逻辑门及其基本连接描述)模拟器。
我遇到了堆栈限制的问题所以我做了一些阅读,发现Python没有"尾部调用优化"功能(即随着递归的进行动态删除堆栈条目)
我在这方面主要有两个问题:
1)如果我将堆栈限制提升到sys.setrecursionlimit(15000)
它是否会影响性能(内存 - 我不在乎)?
2)我有什么方法可以规避这个限制,假设我可以没有堆栈跟踪。
我问这个是因为Verilog主要处理可以使用递归函数以优雅方式实现的状态机。
另外,如果我可以添加,在递归函数调用的情况下,如果有错误,我更依赖于导致此错误的输入而不是堆栈跟踪。
我是Python的新手,所以也许专家可能认为Python堆栈跟踪对于调试递归函数调用非常有用......如果是这种情况,我会非常乐意学习如何做到这一点。 / p>
最后,建议在Python中编写递归函数还是应该转移到其他语言?
如果有任何解决方法,我可以继续使用python进行递归函数,我想知道是否有任何性能影响(我可以进行分析)。
答案 0 :(得分:4)
2)有没有办法可以绕过这个限制,假设我可以没有堆栈跟踪。 我问这个是因为Verilog主要处理可以使用递归函数以优雅方式实现的状态机。
有一种方法可以避免尾调用而不会过多地更改现有逻辑,只需重写尾调用以返回thunk并使用trampoline来调用thunk。如果您需要在转换之间传递复杂状态,可以使用continuation passing style传递它们。这种编写代码的方式非常适合编写状态机。
一个例子可能更清楚,假设你开始使用fizzbuzz状态机的递归实现,该状态机使用尾调用将控制传递给下一个转换:
def start():
return increment(0)
def fizz(n):
print 'fizz'
return increment(n)
def buzz(n):
print 'buzz'
return increment(n)
def fizzbuzz(n):
print 'fizzbuzz'
return increment(n)
def increment(n):
n = n + 1
if n > 100:
return terminate()
elif n % 3 == 0 and n % 5 == 0:
return fizzbuzz(n)
elif n % 3 == 0:
return fizz(n)
elif n % 5 == 0:
return buzz(n)
else:
print n
return increment(n)
def terminate():
raise StopIteration
try:
start()
except StopIteration:
pass
要避免尾调用,只需将所有尾调用都包装在lambda(或者functools.partial)中,然后添加一个trampoline:
def start():
return lambda: increment(0)
def fizz(n):
print 'fizz'
return lambda: increment(n)
def buzz(n):
print 'buzz'
return lambda: increment(n)
def fizzbuzz(n):
print 'fizzbuzz'
return lambda: increment(n)
def increment(n):
n = n + 1
if n > 2000:
# strictly speaking, transitions that takes no arguments
# like terminate don't need to be wrapped in lambda
# this is added here only for symmetry with others
return lambda: terminate()
elif n % 3 == 0 and n % 5 == 0:
return lambda: fizzbuzz(n)
elif n % 3 == 0:
return lambda: fizz(n)
elif n % 5 == 0:
return lambda: buzz(n)
else:
print n
return lambda: increment(n)
def terminate():
raise StopIteration
def trampoline(func):
try:
while True:
func = func()
except StopIteration:
pass
trampoline(lambda: start())
现在你可以拥有更多的fizzbuzz而不会达到递归限制。
答案 1 :(得分:2)
很大程度上取决于您尝试实施的递归解决方案的具体性质。让我举一个具体的例子。假设您想要列表中所有值的总和。您可以通过将第一个值添加到列表其余部分的总和来设置递归 - 递归应该是显而易见的。但是,递归子问题仅比原始问题小1,因此递归堆栈将增长到与列表中的项目数一样大。对于大型列表,这将是一个问题。另一种递归是要注意所有值的总和是列表的前半部分加上列表后半部分的总和。同样,递归应该是显而易见的,并且终止条件是当您到达长度为1的子列表时。但是,对于此版本,堆栈将仅增长为列表大小的log 2 ,并且你可以处理巨大的列表没有堆栈问题。并非所有问题都可以考虑到一半大小的子问题,但是当你可以这样做时,这是避免堆栈溢出情况的好方法。
如果递归解决方案是尾递归,则可以轻松转换为循环而不是递归调用。
如果您没有尾递归,另一种可能性是使用循环实现事物并在显式堆栈上显式存储您的中间状态。
答案 2 :(得分:2)
请参阅Does Python optimize tail recursion?
Guido Van Rossum表示,使用大量的递归是“简单的非语言”#34; :http://neopythonic.blogspot.co.uk/2009/04/tail-recursion-elimination.html
但是很多人都试图破解他们自己的支持。例如。 http://tomforb.es/adding-tail-call-optimization-to-python。或者只是google" python tail call"
答案 3 :(得分:0)
注意:这个答案仅限于你最关心的问题,即"是否建议在Python中编写递归函数?"。
简短的回答是否定的,不完全是#34;可取的"。在没有尾调用优化的情况下,考虑到内存和处理器时间上的强大函数调用,递归在Python中会变得非常缓慢。只要有可能,最好迭代地重写代码。
答案 4 :(得分:0)
特别针对标记为1)的问题,更改递归限制是危险的,因为它可能允许底层C堆栈溢出。另请参阅此问题:What is the maximum recursion depth in Python, and how to increase it?
答案 5 :(得分:0)
我使用sys.setrecursionlimit
将递归限制设置为其最大可能值,因为我遇到了大类/函数达到默认最大递归深度的问题。为递归限制设置较大的值不应该影响脚本的性能,即如果它在高递增和低递归限制下完成,则需要相同的时间才能完成。唯一的区别是,如果你有一个低递归限制,它会阻止你做愚蠢的事情(比如运行无限递归循环)。有了一个上限,而不是达到极限,一个使用递归太多的非常低效的脚本将永远运行(或直到它耗尽内存,具体取决于任务)。
正如其他答案更详细地解释的那样,大多数情况下,除了一系列递归调用之外,还有一种更快的方式来做你正在做的事情。
答案 6 :(得分:-1)
我已经看到装饰器试图在python中实现尾递归,所以我自己做了一个刺。这是一个纯粹的python(没有sys._getframe)尾递归优化实现,允许相互递归。
class TailRecurseException(Exception):
def __init__(self, func, args, kwargs):
self.func = func
self.args = args
self.kwargs = kwargs
def tail_recursive(g, rec=[]):
def func(*args, **kwargs):
if g in rec:
raise TailRecurseException(g, args, kwargs)
rec.append( g )
while True:
try:
r = g(*args, **kwargs)
rec.remove( g )
return r
except TailRecurseException, e:
if e.func==g:
args = e.args
kwargs = e.kwargs
else:
rec.remove( g )
raise e
return func
@tail_recursive
def g(n):
if n==0:
return 0
else:
return f(n-1)
@tail_recursive
def f(n):
if n == 0:
return 0
else:
return g(n-1)
print f(100000)