建议用Python编写递归函数吗

建议用Python编写递归函数吗,python,recursion,Python,Recursion,作为实验的一部分,我用python编写了一个verilog(基本上是逻辑门及其连接性描述)模拟器 我遇到了堆栈限制的问题,所以读了一些书,发现Python没有“尾部调用优化”功能(即在递归过程中动态删除堆栈项) 在这方面,我主要有两个问题: 1) 如果我将堆栈限制提升到sys.setrecursionlimit(15000)是否会影响时间性能(内存--我不在乎) 2) 假设我可以在没有堆栈跟踪的情况下生存,是否有任何方法可以绕过此限制。 我这样问是因为Verilog主要处理状态机,它可以使用递归

作为实验的一部分,我用python编写了一个verilog(基本上是逻辑门及其连接性描述)模拟器

我遇到了堆栈限制的问题,所以读了一些书,发现Python没有“尾部调用优化”功能(即在递归过程中动态删除堆栈项)

在这方面,我主要有两个问题:

1) 如果我将堆栈限制提升到
sys.setrecursionlimit(15000)
是否会影响时间性能(内存--我不在乎)

2) 假设我可以在没有堆栈跟踪的情况下生存,是否有任何方法可以绕过此限制。
我这样问是因为Verilog主要处理状态机,它可以使用递归函数以优雅的方式实现

另外,如果我可以补充一点,在递归函数调用的情况下,如果有bug,我更依赖于导致该bug的输入,而不是堆栈跟踪

我是Python新手,所以专家可能会说Python堆栈跟踪对于调试递归函数调用非常有用……如果是这样,我将非常乐意学习如何做到这一点

最后,是建议用Python编写递归函数,还是应该改用其他语言


如果有任何解决方法可以让我继续使用python实现递归函数,我想知道是否有任何性能影响(不过我可以进行评测)。

很大程度上取决于您尝试实现的递归解决方案的具体性质。让我举一个具体的例子。假设您想要列表中所有值的总和。您可以通过将第一个值添加到列表其余部分的总和来设置递归-递归应该是显而易见的。但是,递归子问题仅比原始问题小1个,因此递归堆栈将增长到与列表中的项数一样大。对于大型列表,这将是一个问题。另一种递归方法是注意所有值的总和是列表前半部分加上列表后半部分的总和。同样,递归应该是显而易见的,终止条件是当您进入长度为1的子列表时。然而,对于这个版本,堆栈只会随着列表大小的log2增长,并且您可以处理巨大的列表而不会出现堆栈问题。并不是所有的问题都可以分解成一半大小的子问题,但如果可以,这是避免堆栈溢出情况的一个好方法

如果您的递归解决方案是尾部递归,那么您可以轻松地转换为循环而不是递归调用


如果没有尾部递归,另一种可能性是使用循环实现,并将中间状态显式存储在显式堆栈上

注意:这个答案仅限于您最重要的问题,即“是否建议用Python编写递归函数?”


简而言之,答案是否定的,这并不完全是“明智的”。如果没有尾部调用优化,递归在Python中会变得非常缓慢,因为函数调用在内存和处理器时间上都非常密集。只要有可能,最好以迭代方式重写代码。

专门解决标记为1)的问题,更改递归限制是危险的,因为它可能会导致底层C堆栈溢出。另请看这个问题:

我曾看到装饰程序试图在python中实现尾部递归,所以我自己尝试过。下面是一个纯python(无sys.\u 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)

Guido Van Rossum说,使用大量递归是“简单的非音速”:


但不管怎样,许多人都试图获得自己的支持。例如。或者只需谷歌“python tail call”

我使用
sys.setrecursionlimit
将递归限制设置为其可能的最大值,因为我遇到了大型类/函数达到默认最大递归深度的问题。为递归限制设置一个较大的值不应影响脚本的性能,也就是说,如果在高递归限制和低递归限制下完成,则需要相同的时间来完成。唯一的区别是,如果递归限制很低,它会阻止你做愚蠢的事情(比如运行无限递归循环)。有了上限,而不是达到上限,一个效率极低、使用递归太多的脚本将永远运行(或者直到根据任务耗尽内存)

正如其他答案更详细地解释的那样,大多数情况下,除了一长串递归调用之外,还有一种更快的方法来完成您正在做的任何事情

2) 假设我可以在没有堆栈跟踪的情况下生存,有什么方法可以绕过这个限制吗。 我这样问是因为Verilog主要处理状态机,它可以使用递归函数以优雅的方式实现

有一种方法可以避免尾部调用,而不需要太多地更改现有逻辑,只需重写尾部调用以返回thunk,然后使用调用来调用该thunk。如果需要在转换之间以复杂状态传递,可以使用来传递它们。这种编写代码的风格非常适合于编写状态机

一个例子可能更清楚,假设您从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)中并添加一个蹦床:

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。

因此,您建议使用fork-join方法。。。你的建议意味着我必须重组我的课程(意义重大)…我问这个问题的主要动机是想看看我是否可以选择一些