Ich habe diese rekursive Schwanzfunktion hier:
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))
Es funktioniert bis n=997
, dann bricht es einfach und spuckt a aus RecursionError: maximum recursion depth exceeded in comparison
. Ist das nur ein Stapelüberlauf? Gibt es eine Möglichkeit, das zu umgehen?
line <n>, in <module>
In-Stack-Traces) und dieser Code benötigt 2 Stack-Framesn=1
(da der Basisfall so istn < 1
, dassn=1
er immer noch wiederholt wird). Und ich denke, das Rekursionslimit ist nicht inklusive, wie in "Fehler, wenn Sie 1000 treffen", nicht "Fehler, wenn Sie 1000 (1001) überschreiten".997 + 2
ist weniger als 1000, also funktioniert998 + 2
es nicht, weil es das Limit erreicht.recursive_function(997)
funktioniert, es bricht bei998
. Wenn Sie es aufrufenrecursive_function(998)
, werden 999 Stack-Frames verwendet und 1 Frame wird vom Interpreter hinzugefügt (da Ihr Code immer so ausgeführt wird, als ob er Teil des Top-Level-Moduls ist), wodurch das 1000-Limit erreicht wird.Antworten:
Es ist ein Schutz gegen einen Stapelüberlauf, ja. Python (oder besser gesagt die CPython-Implementierung) optimiert die Schwanzrekursion nicht, und eine ungezügelte Rekursion führt zu Stapelüberläufen. Sie können das Rekursionslimit mit überprüfen und das Rekursionslimit mit
sys.getrecursionlimit
ändernsys.setrecursionlimit
, dies ist jedoch gefährlich - das Standardlimit ist ein wenig konservativ, aber Python-Stackframes können ziemlich groß sein.Python ist keine funktionale Sprache und die Schwanzrekursion ist keine besonders effiziente Technik. Wenn möglich, ist es im Allgemeinen besser, den Algorithmus iterativ umzuschreiben.
quelle
sys
denresource
Modulen als auch in den Modulen erhöhenSieht so aus, als müssten Sie nur eine höhere Rekursionstiefe festlegen :
quelle
Dies dient dazu, einen Stapelüberlauf zu vermeiden. Der Python-Interpreter begrenzt die Rekursionstiefe, um unendliche Rekursionen zu vermeiden, die zu Stapelüberläufen führen. Versuchen Sie, das Rekursionslimit (
sys.setrecursionlimit
) zu erhöhen oder Ihren Code ohne Rekursion neu zu schreiben.Aus der Python-Dokumentation :
quelle
Wenn Sie das Rekursionslimit häufig ändern müssen (z. B. beim Lösen von Programmierrätseln), können Sie einen einfachen Kontextmanager wie folgt definieren :
Um eine Funktion mit einem benutzerdefinierten Limit aufzurufen, können Sie Folgendes tun:
Beim Verlassen des Hauptteils der
with
Anweisung wird das Rekursionslimit auf den Standardwert zurückgesetzt.quelle
resource
. Ohne diesen Fehler wird ein Segmentierungsfehler angezeigt, und der gesamte Python-Prozess stürzt ab, wenn Siesetrecursionlimit
zu hoch sind und versuchen, das neue Limit zu verwenden (ca. 8 Megabyte Stapelrahmen, was mit der oben beschriebenen einfachen Funktion ~ 30.000 Stapelrahmen bedeutet) mein Laptop).Verwenden Sie eine Sprache, die eine Tail-Call-Optimierung garantiert. Oder verwenden Sie die Iteration. Alternativ können Sie auch mit Dekorateuren süß werden .
quelle
ulimit -s
, ja, es ist stackoverflow.com/a/50120316resource.setrlimit
muss auch verwendet werden, um die Stapelgröße zu erhöhen und Segfault zu verhindernDer Linux-Kernel begrenzt den Stapel von Prozessen .
Python speichert lokale Variablen auf dem Stapel des Interpreters, sodass die Rekursion den Stapelspeicher des Interpreters beansprucht.
Wenn der Python-Interpreter versucht, das Stapellimit zu überschreiten, macht der Linux-Kernel einen Segmentierungsfehler.
Die Stapelbegrenzungsgröße wird mit den Systemaufrufen
getrlimit
undsetrlimit
gesteuert.Python bietet über das
resource
Modul Zugriff auf diese Systemaufrufe .Wenn Sie das Ulimit weiter erhöhen, wird Ihr RAM natürlich knapp, was entweder Ihren Computer aufgrund von Swap-Wahnsinn zum Stillstand bringt oder Python über den OOM Killer tötet.
In Bash können Sie das Stapellimit (in KB) anzeigen und festlegen mit:
Der Standardwert für mich ist 8 MB.
Siehe auch:
Getestet unter Ubuntu 16.10, Python 2.7.12.
quelle
rlimit_stack
nach Stack Clash- Korrekturen eine Einstellung vorzunehmen, kann zu Fehlern oder damit verbundenen Problemen führen. Siehe auch Red Hat Ausgabe 1463241Mir ist klar, dass dies eine alte Frage ist, aber für diejenigen, die lesen, würde ich empfehlen, bei Problemen wie diesen keine Rekursion zu verwenden - Listen sind viel schneller und vermeiden Rekursion vollständig. Ich würde dies implementieren als:
(Verwenden Sie n + 1 in xrange, wenn Sie Ihre Fibonacci-Sequenz von 0 statt 1 zählen.)
quelle
xrange
heißt einfachrange
, in Python 3.Natürlich können Fibonacci-Zahlen in O (n) unter Anwendung der Binet-Formel berechnet werden:
Wie die Kommentatoren bemerken, ist es nicht O (1), sondern O (n) wegen
2**n
. Ein Unterschied besteht auch darin, dass Sie nur einen Wert erhalten, während Sie bei der Rekursion alle WerteFibonacci(n)
bis zu diesem Wert erhalten.quelle
n
aufgrund von Gleitkomma-Ungenauigkeiten bei größeren Fehlern fehlschlägt - der Unterschied zwischen(1+sqrt(5))**n
und(1+sqrt(5))**(n+1)
wird kleiner als 1 ulp, sodass Sie falsche Ergebnisse erhalten.(1+sqrt(5))**n
und((1+sqrt(5))**n)+1
das wird weniger als 1 ulp! (kleiner Tippfehler) Auch {@} rwst Das ist nicht O (1)! Die Berechnung2**n
dauert mindestens O (n).2**n
ist effektiv O (log (n)) unter Verwendung der Exponentiattion durch Quadrieren .Ich hatte ein ähnliches Problem mit dem Fehler "Maximale Rekursionstiefe überschritten". Ich habe festgestellt, dass der Fehler durch eine beschädigte Datei in dem Verzeichnis ausgelöst wurde, mit dem ich eine Schleife durchgeführt habe
os.walk
. Wenn Sie Probleme bei der Lösung dieses Problems haben und mit Dateipfaden arbeiten, sollten Sie es eingrenzen, da es sich möglicherweise um eine beschädigte Datei handelt.quelle
Wenn Sie nur wenige Fibonacci-Zahlen erhalten möchten, können Sie die Matrixmethode verwenden.
Es ist schnell, da numpy einen schnellen Exponentiationsalgorithmus verwendet. Sie erhalten eine Antwort in O (log n). Und es ist besser als Binets Formel, weil es nur ganze Zahlen verwendet. Wenn Sie jedoch alle Fibonacci-Zahlen bis zu n möchten, ist es besser, dies durch Auswendiglernen zu tun.
quelle
Generatoren verwenden?
über fib () Funktion angepasst von: http://intermediatepythonista.com/python-generators
quelle
[fibs().next() for ...]
darin, dass jedes Mal ein neuer Generator erstellt wird.Viele empfehlen, dass das Erhöhen des Rekursionslimits eine gute Lösung ist, jedoch nicht, weil es immer ein Limit gibt. Verwenden Sie stattdessen eine iterative Lösung.
quelle
Wie von @alex vorgeschlagen , können Sie eine Generatorfunktion verwenden , um dies nacheinander anstatt rekursiv durchzuführen .
Hier ist das Äquivalent des Codes in Ihrer Frage:
quelle
Ich wollte Ihnen ein Beispiel für die Verwendung der Memoisierung zur Berechnung von Fibonacci geben, da Sie auf diese Weise mithilfe der Rekursion erheblich größere Zahlen berechnen können:
Dies ist immer noch rekursiv, verwendet jedoch eine einfache Hashtabelle, mit der zuvor berechnete Fibonacci-Zahlen wiederverwendet werden können, anstatt sie erneut auszuführen.
quelle
quelle
Wir können das mit
@lru_cache
Dekorateur undsetrecursionlimit()
Methode tun :Ausgabe
Quelle
functools lru_cache
quelle
Wir könnten auch eine Variation des Bottom-up-Ansatzes der dynamischen Programmierung verwenden
quelle