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


421

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

def recursive_function(n, sum):
    if n < 1:
        return sum
    else:
        return recursive_function(n-1, sum+n)

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

它工作到了n=997,然后它破裂并吐出了RecursionError: maximum recursion depth exceeded in comparison。这仅仅是堆栈溢出吗?有办法解决吗?



9
记忆可以通过使以前计算的值终止而不是增加堆栈大小来加快功能并增加其有效递归深度。
Cyoce

2
递归限制通常为1000。–
Boris

1
@tonix解释器添加一个堆栈帧(line <n>, in <module>堆栈中的跟踪),并且此代码需要2个堆栈帧n=1(因为基本情况是n < 1,因此n=1它仍然递归)。而且我猜想递归限制不包括在内,因为它是“命中1000时出错”而不是“如果超过1000(1001)则出错”。997 + 2小于1000,因此无法工作998 + 2,因为它达到了极限。
鲍里斯(Boris)

1
@tonix号 recursive_function(997)有效,它在中断998。当您调用recursive_function(998)它时,它使用999个堆栈帧,并且解释器添加了1帧(因为您的代码始终像顶级模块的一部分一样运行),这使其达到了1000个限制。
鲍里斯(Boris)

Answers:


469

这是防止堆栈溢出的保护措施,是的。Python(或更确切地说,CPython实现)无法优化尾部递归,无限制的递归会导致堆栈溢出。您可以使用来检查递归限制,sys.getrecursionlimit并使用来更改递归限制sys.setrecursionlimit,但是这样做很危险-标准限制有些保守,但是Python堆栈框架可能会很大。

Python不是一种功能语言,尾部递归并不是一种特别有效的技术。如果可能的话,迭代地重写算法通常是一个更好的主意。


4
根据我的经验,您需要同时增加sysresource模块中的限制:stackoverflow.com/a/16248113/205521
Thomas Ahle 2014年

3
作为将其转换为迭代版本的策略,可以使用尾部调用优化修饰符
jfs 2014年

3
您可以使用svn.python.org/projects/python/trunk/Tools/scripts/…来确定您的操作系统上限
Ullullu 2015年

8
对于那些对源代码感兴趣的人,默认递归限制设置为1000 hg.python.org/cpython/file/tip/Python/ceval.c#l691,可以使用hg.python.org/cpython上的API对其进行更改/file/tip/Python/sysmodule.c#l643,从而将限制设置为hg.python.org/cpython/file/tip/Python/ceval.c#l703
Pramod

16
尾递归是针对其进行优化的编程语言中的一种非常有效的技术。对于正确的问题,迭代实现可能更具表达性。答案的意思可能是“专门在Python中”,但这不是它的意思
Peter R

135

看起来您只需要设置更高的递归深度即可

import sys
sys.setrecursionlimit(1500)

在我的情况下,我忘了在基本情况下的return语句,它超过了1000。Python开始抛出此异常,我很惊讶,因为我确定不会。堆栈将创建以运行它。
vijayraj34

如果您的程序正在进入递归并且您希望错误消息不是页面和相同文本的页面,则sys.setrecursionlimit(50)或少量值很有用。我发现这在调试错误的递归代码时非常有帮助。
peawormsworth

56

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

Python文档中

sys.getrecursionlimit()

返回递归限制的当前值,即Python解释器堆栈的最大深度。此限制可防止无限递归导致C堆栈溢出和Python崩溃。可以通过设置setrecursionlimit()


在我的蟒蛇64位,3.5 Windows上的Python,默认限制是1000
纪尧姆士

30

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

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)

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

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

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


您还想使用来提高流程的递归限制resource。没有它,您将遇到分段错误,如果您setrecursionlimit过高并尝试使用新的限制(大约8兆字节的堆栈帧,使用上面的简单函数可以转换为30,000个堆栈帧),整个Python进程将崩溃。我的笔记本电脑)。
鲍里斯(Boris)

16

使用保证尾叫优化的语言。或使用迭代。另外,也可以和装饰工一起变得可爱。


36
那真是把婴儿带上洗澡水。
罗素·博罗戈夫

3
@罗素:我提供的选项中只有一个可以提供建议。
Marcelo Cantos

“与装饰员变得可爱”并不是完全可以的选择。
B先生

@ Mr.B,除非您需要ulimit -s的堆栈帧数量更多,是的,它是stackoverflow.com/a/50120316
Boris,

14

resource.setrlimit 还必须用于增加堆栈大小并防止段错误

Linux内核限制了进程的堆栈

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

如果Python解释器试图超过堆栈限制,则Linux内核会使其出现分段错误。

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

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

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 Killer杀死Python。

在bash中,您可以使用以下命令查看和设置堆栈限制(以kb为单位):

ulimit -s
ulimit -s 10000

我的默认值为8Mb。

也可以看看:

已在Ubuntu 16.10,Python 2.7.12上测试。


1
rlimit_stack堆栈冲突修复后尝试设置可能会导致失败或相关问题。另请参阅《红帽》第1463241号
jww

我使用了这个(Python资源部分)来帮助我在Tim Roughgarden教授的均值(巨大)数据集上实现Kosaraju的算法。我的实现工作在小集合上,当然,大数据集的问题是递归/堆栈限制...或者是吗?好吧,是的!谢谢!
nilo

9

我意识到这是一个老问题,但是对于那些阅读者,我建议不要对此类问题使用递归-列表要快得多,并且完全避免递归。我将其实现为:

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。)


13
为什么可以使用O(1)时使用O(n)空间?
Janus Troelsen 2014年

11
万一O(n)空间的注释令人困惑:请勿使用列表。当您需要的只是第n个值时,列表将保留所有值。一种简单的算法是保留最后两个斐波那契数字并将其相加,直到获得所需的数字。也有更好的算法。
Milimetric

3
@Mathime:xrange被简称range,在Python 3
埃里克ØLebigot

1
@EOL我知道这一点
Mathime '16

7
@Mathime我对那些阅读这些评论的人很明确。
Eric O Lebigot

9

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

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)


8
python中没有long的最大大小。
pppery

8
值得一提的是失败较大n,因为浮点不精确的-之间的差异(1+sqrt(5))**n(1+sqrt(5))**(n+1)小于1个ULP,所以你开始不正确的结果。

2
NumPy中实际上没有大整数……
Eric O Lebigot

@Mego什么?这之间的区别(1+sqrt(5))**n,并((1+sqrt(5))**n)+1变成小于1个ULP!(小的错别字)而且,{@} rwst不是O(1)!计算2**n至少需要O(n)时间。
user202729 '18

3
@ user202729是不对的,2**n使用平方实际上是O(log(n))的计算。
山姆

6

错误“超出最大递归深度”时,我遇到了类似的问题。我发现错误是由我遍历的目录中的损坏文件触发的os.walk。如果您无法解决此问题,并且正在使用文件路径,请确保将其范围缩小,因为它可能是损坏的文件。


2
OP确实给出了他的代码,并且他的实验可以随意复制。它不涉及损坏的文件。
T. Verron'3

5
您是对的,但我的答案不适合OP,因为这已经超过4年了。我的回答旨在帮助那些由于文件损坏而间接导致MRD错误的人-因为这是最早的搜索结果之一。由于有人投票,它对某人有所帮助。感谢您的不赞成投票。
泰勒(Tyler)

2
这是我在将“最大递归深度”回溯关联到损坏的文件的问题时在任何地方找到的唯一内容。谢谢!
杰夫(Jeff)

5

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

from numpy import matrix

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

由于numpy使用快速指数运算算法,因此速度很快。您会在O(log n)中得到答案。它比Binet的公式更好,因为它仅使用整数。但是,如果您希望所有斐波那契数均不超过n,那么最好通过记忆来实现。


可悲的是,您不能在大多数有竞争力的程序设计专家中使用numpy。但是,先生,您的解决方案是我的最爱。我已经将矩阵求解用于一些问题。当您需要很大的斐波纳契数并且不能使用模数时,这是最佳解决方案。如果允许使用模数,则使用pisano周期是更好的方法。
mentatkgs

4

使用发电机?

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
必须将生成器分配给变量的原因是因为[fibs().next() for ...]每次都会生成一个新生成器。
tox123

3

正如@alex 建议的那样,您可以使用生成器函数按顺序执行此操作,而不必递归执行。

这与您问题中的代码等效:

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

2

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

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

1

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

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))

这仍然是递归的,但是使用了一个简单的哈希表,该哈希表允许重新使用先前计算的斐波那契数,而不是再次进行处理。


1
import sys
sys.setrecursionlimit(1500)

def fib(n, sum):
    if n < 1:
        return sum
    else:
        return fib(n-1, sum+n)

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

1
多次给出相同的答案。请删除它。
ZF007

0

我们可以使用@lru_cache装饰器和setrecursionlimit()方法来做到这一点:

import sys
from functools import lru_cache

sys.setrecursionlimit(15000)


@lru_cache(128)
def fib(n: int) -> int:
    if n == 0:
        return 0
    if n == 1:
        return 1

    return fib(n - 2) + fib(n - 1)


print(fib(14000))

输出量

30024687611784610909954941797150256486927479374907929434683754295022302429422848358634023335752162178658116387303893522391813423077567204146193912177985425759965410810605019053021570190026149647173108088094786756027114403612415007326991458343778563263940370716662743216573053208040553070210197932517628308167015873869948880323622321982198435498652758806996123592751252434571324967728548865087033966433650424543330098020063842868595816492963908030032326548984645615892344451398632426062857115917462228808073910572119126558184997987209873025407120679598408021068497765475222474299046183573947717256532535593461952826012850191693602073551792238148571064052850079975476925463787570629995816578671884209957706505655213778743330859631234442589530527514612069776150795114358628796784390811755362655769771068650740995128972351005382411964458155682913778466563529792280989115666759565256441826456081786038371722278388967254256057199423000376505262314868810660373978669420138382967692847455277784392729950672314920693691302891547531323138832943985935078735556672110054220032041561548590315294621529531199575971957359536867988711311482550501404508450342400953050944499115785985396588557041582402218095280101794144934995834735688732530679216395139965967382758179096248575936932919808413032911456135664665752332836514201349157649613728759338222629534204445483491804365831832919448755994772408147745801871446379654872505781349904024433656779853884819614924449819945230342456197818533654765527194609607959296668836657042938973102012760116580743591941893596607924960274722264285715479716022598086974414353585784805898377669116842002756368891922547626785125970004526761913744759327966638428657446582649249137716764154041799200960747515164228729976654250474574283272762300592961327227879153001050020190062933200829553787159082636533777550311557940634505157310094024075846831328702063769940259207902985911442136599426686220621914413462000983429439551695225325742716449543602174724585214896718594652325684194041820439660922117443726997973759660480107754534446001535247722384014147895626514102898089949605331327595320928957794069409252529061666121536998507599337628979471759721478687840083202475862103785567113327394632779402552890479623233069460683818874460463877452479256752401829811908362649646406120699094586824433927299460840993120477529668064393314036639349699429580222379452059925811788036061569820343853471827665733517687496651725499086383376119531998081619378853667092850432765957264840681380911889146981517031227737267252613705423551621181643027288122591924764289387307241098259223319732561050912005515665813505080619227629100785282198699132141465755572491992636342411653522265707496189070505531154683066691844859102698062258945308098231022792317500616520425607725305767131486478587056496429077806032476806802362362202208266406656599026501804747607137957607601654671

资源

functools lru_cache


0

我们还可以使用动态编程自底向上方法的变体

def fib_bottom_up(n):

    bottom_up = [None] * (n+1)
    bottom_up[0] = 1
    bottom_up[1] = 1

    for i in range(2, n+1):
        bottom_up[i] = bottom_up[i-1] + bottom_up[i-2]

    return bottom_up[n]

print(fib_bottom_up(20000))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.