Qual è la profondità massima di ricorsione in Python e come aumentarla?


422

Ho questa funzione ricorsiva della coda qui:

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

Funziona fino a n=997, quindi si rompe e sputa a RecursionError: maximum recursion depth exceeded in comparison. È solo un overflow dello stack? C'è un modo per aggirarlo?



9
la memoizzazione potrebbe velocizzare la tua funzione e aumentarne l'effettiva profondità ricorsiva facendo terminare i valori calcolati in precedenza anziché aumentare le dimensioni dello stack.
Cyoce,

2
Il limite di ricorsione è di solito 1000.
Boris

1
@tonix l'interprete aggiunge un frame di stack (le line <n>, in <module>tracce in stack) e questo codice prende 2 frame di stack per n=1(perché il caso base lo è n < 1, quindi per il n=1fatto che ricorre ancora). E immagino che il limite di ricorsione non sia inclusivo, come nel suo "errore quando si preme 1000" non "errore se si supera 1000 (1001)". 997 + 2è inferiore a 1000 quindi non funziona 998 + 2perché raggiunge il limite.
Boris,

1
@tonix no. recursive_function(997)funziona, si rompe a 998. Quando lo chiami recursive_function(998)usa 999 frame stack e 1 frame viene aggiunto dall'interprete (perché il tuo codice viene sempre eseguito come se facesse parte del modulo di livello superiore), il che lo fa raggiungere il limite di 1000.
Boris,

Risposte:


469

È una guardia contro un overflow dello stack, sì. Python (o meglio, l'implementazione di CPython) non ottimizza la ricorsione della coda e la ricorsione sfrenata causa overflow dello stack. Puoi controllare il limite di ricorsione con sys.getrecursionlimite modificare il limite di ricorsione con sys.setrecursionlimit, ma farlo è pericoloso - il limite standard è un po 'conservativo, ma gli stackframe Python possono essere piuttosto grandi.

Python non è un linguaggio funzionale e la ricorsione della coda non è una tecnica particolarmente efficiente. Riscrivere l'algoritmo in modo iterativo, se possibile, è generalmente un'idea migliore.


4
Dalla mia esperienza, è necessario aumentare il limite sia nei moduli che sysnei resourcemoduli: stackoverflow.com/a/16248113/205521
Thomas Ahle,

3
come tattica per convertirlo in una versione iterativa, si potrebbe usare un decoratore per l'ottimizzazione delle chiamate di coda
jfs

3
puoi usare svn.python.org/projects/python/trunk/Tools/scripts/… per scoprire il tuo limite superiore del sistema operativo
Ullullu,

8
Per coloro che sono interessati alla fonte, il limite di ricorsione predefinito è impostato su 1000 hg.python.org/cpython/file/tip/Python/ceval.c#l691 e può essere modificato utilizzando l'API su hg.python.org/cpython /file/tip/Python/sysmodule.c#l643 che a sua volta imposta il limite al nuovo valore su hg.python.org/cpython/file/tip/Python/ceval.c#l703
Pramod

17
La ricorsione della coda è una tecnica perfettamente efficiente in un linguaggio di programmazione ottimizzato per questo. Per il giusto tipo di problema, può essere notevolmente più espressivo e un'implementazione iterativa. La risposta probabilmente significa "in Python in particolare" ma non è quello che dice
Peter R

136

Sembra che tu abbia solo bisogno di impostare una profondità di ricorsione più alta :

import sys
sys.setrecursionlimit(1500)

Nel mio caso ho dimenticato la dichiarazione di ritorno nel caso base e ha continuato a superare 1000. Python ha iniziato a lanciare questa eccezione e sono rimasto sorpreso, perché ero sicuro del no. di stack sta per creare per eseguirlo.
vijayraj34

sys.setrecursionlimit (50) o una piccola quantità è utile se il programma sta inserendo la ricorsione e si desidera che il messaggio di errore NON sia pagine e pagine dello stesso testo. L'ho trovato molto utile durante il debug (mio) codice ricorsivo errato.
Peawormsworth,

56

È per evitare un overflow dello stack. L'interprete Python limita le profondità di ricorsione per aiutarti a evitare ricorsioni infinite, con conseguente overflow dello stack. Prova ad aumentare il limite di ricorsione ( sys.setrecursionlimit) o riscrivi il codice senza ricorsione.

Dalla documentazione di Python :

sys.getrecursionlimit()

Restituisce il valore corrente del limite di ricorsione, la profondità massima dello stack dell'interprete Python. Questo limite impedisce la ricorsione infinita causando un overflow dello stack C e l'arresto anomalo di Python. Può essere impostato da setrecursionlimit().


Sul mio Anaconda x64, 3.5 Python su Windows, il limite predefinito è 1000.
Guillaume Chevalier

30

Se è spesso necessario modificare il limite di ricorsione (ad esempio durante la risoluzione di enigmi di programmazione) è possibile definire un semplice gestore di contesto come questo:

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)

Quindi per chiamare una funzione con un limite personalizzato puoi fare:

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

All'uscita dal corpo withdell'istruzione, il limite di ricorsione verrà ripristinato al valore predefinito.


Si desidera inoltre aumentare il limite di ricorsione del processo conresource . Senza di essa, otterrai un errore di segmentazione e l'intero processo Python si arresterà in modo anomalo se sei setrecursionlimittroppo alto e provi a utilizzare il nuovo limite (circa 8 megabyte di frame di stack, che si traduce in ~ 30.000 frame di stack con la semplice funzione sopra, su il mio portatile).
Boris,

16

Utilizzare un linguaggio che garantisca l'ottimizzazione della coda. O usa l'iterazione. In alternativa, diventa carino con i decoratori .


36
Questo è piuttosto buttare via il bambino con l'acqua del bagno.
Russell Borogove,

3
@Russell: solo una delle opzioni che ho offerto lo consiglia.
Marcelo Cantos,

"Diventa carino con i decoratori" non è esattamente un'opzione.
Sig. B,

@ Mr.B a meno che tu non abbia bisogno di qualcosa in più rispetto ulimit -sai frame stack, sì è stackoverflow.com/a/50120316
Boris,

14

resource.setrlimit deve essere utilizzato anche per aumentare le dimensioni dello stack e impedire il segfault

Il kernel di Linux limita lo stack di processi .

Python memorizza le variabili locali nello stack dell'interprete, quindi la ricorsione occupa spazio nello stack dell'interprete.

Se l'interprete Python prova a superare il limite dello stack, il kernel Linux rende l'errore di segmentazione.

La dimensione del limite dello stack viene controllata con le chiamate di sistema getrlimite setrlimit.

Python offre l'accesso a tali chiamate di sistema tramite il resourcemodulo.

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)

Naturalmente, se continui ad aumentare ulimit, la tua RAM si esaurirà, il che rallenterà il tuo computer a causa della follia di scambio, o ucciderà Python tramite OOM Killer.

Da bash, puoi vedere e impostare il limite di stack (in kb) con:

ulimit -s
ulimit -s 10000

Il valore predefinito per me è 8 Mb.

Guarda anche:

Testato su Ubuntu 16.10, Python 2.7.12.


1
Il tentativo di impostare rlimit_stackdopo le riparazioni di Stack Clash può causare errori o problemi correlati. Vedi anche Red Hat, numero 1463241
jww,

Ho usato questo (la parte delle risorse di Python) per aiutare la mia implementazione dell'algoritmo di Kosaraju sul set di dati medio (enorme) del professor Tim Roughgarden. La mia implementazione ha funzionato su piccoli set, sicuramente il problema con un set di dati di grandi dimensioni era il limite di ricorsione / stack ... O era? Beh, sì lo era! Grazie!
nilo,

9

Mi rendo conto che questa è una vecchia domanda, ma per coloro che leggono, raccomanderei di non ricorrere alla ricorsione per problemi come questo: gli elenchi sono molto più veloci ed evitano del tutto la ricorsione. Lo implementerei come:

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

(Usa n + 1 in xrange se inizi a contare la sequenza di fibonacci da 0 invece di 1.)


13
perché usare O (n) spazio quando puoi usare O (1)?
Janus Troelsen,

11
Nel caso in cui il commento sullo spazio O (n) fosse confuso: non usare un elenco. Elenco manterrà tutti i valori quando tutto ciò di cui hai bisogno è l'ennesimo valore. Un semplice algoritmo sarebbe quello di mantenere gli ultimi due numeri di fibonacci e aggiungerli fino ad arrivare a quello di cui hai bisogno. Ci sono anche algoritmi migliori.
Milimetrico

3
@Mathime: xrangesi chiama semplicemente range, in Python 3.
Eric O Lebigot

1
@EOL Ne sono consapevole
Mathime,

7
@Mathime Stavo rendendo le cose esplicite per coloro che leggono questi commenti.
Eric O Lebigot,

9

Naturalmente i numeri di Fibonacci possono essere calcolati in O (n) applicando la formula di Binet:

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

Come notano i commentatori, non è O (1) ma O (n) a causa di 2**n. Inoltre, la differenza è che ottieni un solo valore, mentre con la ricorsione ottieni tutti i valori Fibonacci(n)fino a quel valore.


8
Non esiste una dimensione massima di un long in Python.
pppery

8
Vale la pena notare che questo fallisce in misura maggiore a ncausa dell'imprecisione in virgola mobile - la differenza tra (1+sqrt(5))**ne (1+sqrt(5))**(n+1)diventa inferiore a 1 ulp, quindi inizi a ottenere risultati errati.

2
In realtà non ci sono grandi numeri interi in NumPy ...
Eric O Lebigot

@Mego Cosa? È la differenza tra (1+sqrt(5))**ne ((1+sqrt(5))**n)+1che diventa meno di 1 ulp! (piccolo errore di battitura) Inoltre, {@} prima non è O (1)! Il calcolo 2**nrichiede almeno O (n) tempo.
user202729

3
@ user202729 Questo non è vero, il calcolo 2**nè effettivamente O (log (n)) usando Exponentiattion per quadratura .
Sam,

6

Ho avuto un problema simile con l'errore "Superata la profondità massima di ricorsione". Ho scoperto che l'errore era stato innescato da un file corrotto nella directory con cui ero in loop os.walk. Se hai problemi a risolvere questo problema e stai lavorando con i percorsi dei file, assicurati di restringerlo, poiché potrebbe essere un file corrotto.


2
L'OP fornisce il suo codice e il suo esperimento è riproducibile a piacimento. Non comporta file corrotti.
T. Verron

5
Hai ragione, ma la mia risposta non è orientata verso il PO, dato che è avvenuta oltre quattro anni fa. La mia risposta ha lo scopo di aiutare quelli con errori MRD indirettamente causati da file corrotti, poiché questo è uno dei primi risultati della ricerca. Ha aiutato qualcuno, dato che è stato votato. Grazie per il voto negativo.
Tyler

2
Questa è stata l'unica cosa che ho trovato ovunque durante la ricerca del mio problema che collegava un traceback "profondità massima di ricorsione" a un file danneggiato. Grazie!
Jeff,

5

Se vuoi ottenere solo pochi numeri di Fibonacci, puoi usare il metodo matrix.

from numpy import matrix

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

È veloce come numpy usa l'algoritmo di esponenziazione veloce. Ottieni risposta in O (registro n). Ed è meglio della formula di Binet perché utilizza solo numeri interi. Ma se vuoi tutti i numeri di Fibonacci fino a n, allora è meglio farlo memorizzando.


Purtroppo non è possibile usare numpy nella maggior parte dei giudici di programmazione competitivi. Ma sì signore, la tua soluzione è la mia preferita. Ho usato la soluzione matrice per alcuni problemi. È la soluzione migliore quando hai bisogno di un numero di fibonacci molto grande e non puoi usare un modulo. Se ti è permesso usare un modulo, il periodo pisano è il modo migliore per farlo.
mentatkgs

4

Usa i generatori?

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

sopra la funzione fib () adattata da: http://intermediatepythonista.com/python-generators


1
il motivo per cui è necessario assegnare un generatore a una variabile è perché si [fibs().next() for ...]creerebbe un nuovo generatore ogni volta.
tox123

3

Come suggerito da @alex , è possibile utilizzare una funzione di generatore per eseguire questa operazione in sequenza anziché in modo ricorsivo.

Ecco l'equivalente del codice nella tua domanda:

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

Molti raccomandano che l'aumento del limite di ricorsione sia una buona soluzione, tuttavia non è perché ci sarà sempre un limite. Usa invece una soluzione iterativa.

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

1

Volevo darti un esempio per l'utilizzo della memoization per calcolare Fibonacci in quanto ciò ti consentirà di calcolare numeri significativamente più grandi usando la ricorsione:

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

Questo è ancora ricorsivo, ma utilizza una semplice tabella hash che consente il riutilizzo dei numeri di Fibonacci calcolati in precedenza invece di ripeterli.


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
Questa stessa risposta è stata data molte volte. Per favore rimuovilo.
ZF007,

0

Possiamo farlo usando il @lru_cachedecoratore e il setrecursionlimit()metodo:

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

Produzione

3002468761178461090995494179715025648692747937490792943468375429502230242942284835863402333575216217865811638730389352239181342307756720414619391217798542575996541081060501905302157019002614964717310808809478675602711440361241500732699145834377856326394037071666274321657305320804055307021019793251762830816701587386994888032362232198219843549865275880699612359275125243457132496772854886508703396643365042454333009802006384286859581649296390803003232654898464561589234445139863242606285711591746222880807391057211912655818499798720987302540712067959840802106849776547522247429904618357394771725653253559346195282601285019169360207355179223814857106405285007997547692546378757062999581657867188420995770650565521377874333085963123444258953052751461206977615079511435862879678439081175536265576977106865074099512897235100538241196445815568291377846656352979228098911566675956525644182645608178603837172227838896725425605719942300037650526231486881066037397866942013838296769284745527778439272995067231492069369130289154753132313883294398593507873555667211005422003204156154859031529462152953119957597195735953686798871131148255050140450845034240095305094449911578598539658855704158240221809528010179414493499583473568873253067921639513996596738275817909624857593693291980841303291145613566466575233283651420134915764961372875933822262953420444548349180436583183291944875599477240814774580187144637965487250578134990402443365677985388481961492444981994523034245619781853365476552719460960795929666883665704293897310201276011658074359194189359660792496027472226428571547971602259808697441435358578480589837766911684200275636889192254762678512597000452676191374475932796663842865744658264924913771676415404179920096074751516422872997665425047457428327276230059296132722787915300105002019006293320082955378715908263653377755031155794063450515731009402407584683132870206376994025920790298591144213659942668622062191441346200098342943955169522532574271644954360217472458521489671859465232568419404182043966092211744372699797375966048010775453444600153524772238401414789562651410289808994960533132759532092895779406940925252906166612153699850759933762897947175972147868784008320247586210378556711332739463277940255289047962323306946068381887446046387745247925675240182981190836264964640612069909458682443392729946084099312047752966806439331403663934969942958022237945205992581178803606156982034385347182766573351768749665172549908638337611953199808161937885366709285043276595726484068138091188914698151703122773726725261370542355162118164302728812259192476428938730724109825922331973256105091200551566581350508061922762910078528219869913214146575557249199263634241165352226570749618907050553115468306669184485910269806225894530809823102279231750061652042560772530576713148647858705369649642907780603247428680176236527220826640665659902650188140474762163503557640566711903907798932853656216227739411210513756695569391593763704981001125

fonte

functools lru_cache


0

Potremmo anche utilizzare una variazione dell'approccio dal basso verso l'alto della programmazione dinamica

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))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.