Python中的最大递归深度是多少,以及如何增加它?

What is the maximum recursion depth in Python, and how to increase it?

这里我有一个尾部递归函数:

1
2
3
4
5
6
7
8
def fib(n, sum):
    if n < 1:
        return sum
    else:
        return fib(n-1, sum+n)

c = 998
print(fib(c, 0))

它的计算结果是n=997,然后它就中断了,并发出了一个"超过比较中最大递归深度"RuntimeError。这只是堆栈溢出吗?有办法绕过它吗?


它是防止堆栈溢出的保护,是的。python(或者更确切地说,cpython实现)不优化尾部递归,而无限制的递归会导致堆栈溢出。您可以使用sys.setrecursionlimit更改递归限制,但这样做是危险的——标准限制有点保守,但python stackframes可以很大。

python不是一种函数语言,tail递归不是一种特别有效的技术。如果可能,迭代地重写算法通常是一个更好的主意。


看起来你只需要设置一个更高的递归深度

1
sys.setrecursionlimit(1500)


这是为了避免堆栈溢出。python解释器限制递归的深度,以帮助您避免无限递归,从而导致堆栈溢出。尝试增加递归限制(sys.setrecursionlimit)或在不使用递归的情况下重新编写代码。

从python网站:

sys.getrecursionlimit()

Return the current value of the recursion limit, the maximum depth of the Python interpreter stack. This limit prevents infinite recursion from causing an overflow of the C stack and crashing Python. It can be set by setrecursionlimit().


使用确保尾叫优化的语言。或者使用迭代。或者,和装饰师一起玩。


我意识到这是一个古老的问题,但是对于那些阅读,我建议不要使用递归来解决像这样的问题——列表更快,并且完全避免递归。我将按以下方式实施:

1
2
3
4
5
def fibonacci(n):
    f = [0,1,1]
    for i in xrange(3,n):
        f.append(f[i-1] + f[i-2])
    return 'The %.0fth fibonacci number is: %.0f' % (n,f[-1])

(如果从0而不是1开始计算斐波那契序列,请在xrange中使用n+1。)


当然,斐波那契数可以通过应用binet公式在o(n)中计算:

1
2
3
4
from math import floor, sqrt

def fib(n):                                                    
    return int(floor(((1+sqrt(5))**n-(1-sqrt(5))**n)/(2**n*sqrt(5))+0.5))

正如评论者所说,这不是O(1),而是O(n),因为2**n。还有一个区别是,您只能得到一个值,而使用递归,您可以得到该值之前的Fibonacci(n)的所有值。


如果您经常需要更改递归限制(例如,在解决编程难题时),您可以这样定义一个简单的上下文管理器:

1
2
3
4
5
6
7
8
9
10
11
12
import sys

class recursionlimit:
    def __init__(self, limit):
        self.limit = limit
        self.old_limit = sys.getrecursionlimit()

    def __enter__(self):
        sys.setrecursionlimit(self.limit)

    def __exit__(self, type, value, tb):
        sys.setrecursionlimit(self.old_limit)

然后,要调用具有自定义限制的函数,可以执行以下操作:

1
2
with recursionlimit(1500):
    print(fib(1000, 0))

退出with语句体时,递归限制将恢复为默认值。


还必须使用resource.setrlimit来增加堆栈大小并防止segfault

Linux内核限制了进程的堆栈。

Python将局部变量存储在解释器的堆栈上,因此递归占用解释器的堆栈空间。

如果python解释器试图超过堆栈限制,Linux内核会出错。

堆栈限制大小由getrlimitsetrlimit系统调用控制。

python通过resource模块提供对这些系统调用的访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import resource
import sys

print resource.getrlimit(resource.RLIMIT_STACK)
print sys.getrecursionlimit()
print

# Will segfault without this line.
resource.setrlimit(resource.RLIMIT_STACK, [0x10000000, resource.RLIM_INFINITY])
sys.setrecursionlimit(0x100000)

def f(i):
    print i
    sys.stdout.flush()
    f(i + 1)
f(0)

当然,如果你继续增加ulimit,你的RAM将会耗尽,这将使你的计算机由于交换疯狂而停止,或者通过OOM杀手杀死python。

在bash中,您可以看到并设置堆栈限制(以KB为单位):

1
2
ulimit -s
ulimit -s 10000

我的默认值是8MB。

另请参见:

    百万千克1在python脚本中设置stacksize百万千克1百万千克1python:Linux、Mac和Windows的硬递归限制是什么?百万千克1

在Ubuntu 16.10、python 2.7.12上测试。


我有一个类似的错误"超过了最大递归深度"。我发现错误是由我用os.walk循环访问的目录中的一个损坏文件触发的。如果您在解决此问题时遇到问题,并且正在处理文件路径,请确保缩小范围,因为它可能是一个损坏的文件。


如果只想得到几个斐波那契数,可以使用矩阵法。

1
2
3
4
from numpy import matrix

def fib(n):
    return (matrix('0 1; 1 1', dtype='object') ** n).item(1)

它的速度和numpy使用快速求幂算法一样快。在O(登录N)中得到答案。它比binet公式更好,因为它只使用整数。但是如果你想要所有的斐波那契数都达到n,那么最好是通过记忆来实现。


使用发电机?

1
2
3
4
5
6
7
8
9
10
11
12
13
def fib():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fibs = fib() #seems to be the only way to get the following line to work is to
             #assign the infinite generator to a variable

f = [fibs.next() for x in xrange(1001)]

for num in f:
        print num

上面的fib()函数改编自:http://intermediatepythonista.com/python-generators


许多人建议增加递归限制是一个很好的解决方案,但这并不是因为总是有限制。而是使用迭代解。

1
2
3
4
5
6
def fib(n):
    a,b = 1,1
    for i in range(n-1):
        a,b = b,a+b
    return a
print fib(5)

我想给你一个使用记忆法计算斐波那契的例子,因为这将允许你用递归法计算更大的数字:

1
2
3
4
5
6
7
8
9
10
11
12
cache = {}
def fib_dp(n):
    if n in cache:
        return cache[n]
    if n == 0: return 0
    elif n == 1: return 1
    else:
        value = fib_dp(n-1) + fib_dp(n-2)
    cache[n] = value
    return value

print(fib_dp(998))

这仍然是递归的,但是使用了一个简单的哈希表,它允许重用以前计算的斐波那契数,而不是再次使用它们。


正如@alex建议的那样,您可以使用一个生成器函数来执行此操作。下面是您问题中的代码等价物:

1
2
3
4
5
6
7
8
9
10
11
def fib(n):
    def fibseq(n):
       """ Iteratively return the first n Fibonacci numbers, starting from 0"""
        a, b = 0, 1
        for _ in xrange(n):
            yield a
            a, b = b, a + b

    return sum(v for v in fibseq(n))

print format(fib(100000), ',d')  # -> no recursion depth error