2016-07-08 9 views
27

Ich habe eine kleine Funktion gemacht, die die max Rekursion Grenze messen tatsächlich:Maximale Rekursion ist nicht genau das, was sys.getrecursionlimit() beansprucht. Woher?

def f(x): 
    r = x 
    try: 
     r = f(x+1) 
    except Exception as e: 
     print(e) 
    finally: 
     return r 

Um zu wissen, was ich geprüft zu erwarten habe:

In [28]: import sys 

In [29]: sys.getrecursionlimit() 
Out[29]: 1000 

jedoch

In [30]: f(0) 
maximum recursion depth exceeded 
Out[30]: 970 

Die Zahl ist nicht festgelegt, immer um ~ 970, und ändert sich leicht zwischen verschiedenen Instanzen von Python (zB von der Eingabeaufforderung von Spyder zu System cmd).

Bitte beachten Sie, dass ich Ipython auf Python3 verwende.

Was ist los? Warum wird das tatsächliche Limit niedriger als der Wert sys.getrecursionlimit()?

+0

Es ist ein Schutz gegen einen Stapelüberlauf. Sie können das Rekursionslimit mit 'sys.setrecursionlimit' ändern, aber dies ist gefährlich. – dazzieta

+1

Was geschieht, wenn Sie die Rekursionseinschränkung mit 'sys.setrecursionlimit (limit)' (https://docs.python.org/3/library/sys.html#sys.setrecursionlimit) am Anfang Ihres Codes manuell setzen? Siehe auch http://stackoverflow.com/questions/3323001/maximum-recursion-depth und http://stackoverflow.com/questions/5061582/setting-stacksize-in-a-python-script/16248113#16248113 – linusg

+2

Nur ein Randnotiz. Sie sollten Ihren rekursiven Code nicht korrigieren, indem Sie das Rekursionslimit erhöhen, da dies nicht ladungssicher ist. Wenn Sie Rekursion wirklich wollen, dann verwenden Sie TCO und einen Dekorator, um die Tailaufrufe zu beseitigen (es gibt viele). Oder bleibe bei einer imperativen Alternative. –

Antwort

35

Das Rekursionslimit ist nicht das Limit der Rekursion, sondern die maximale Tiefe des Python-Interpreter-Stacks. Es gibt etwas auf dem Stack, bevor die Funktion ausgeführt wird. Spyder führt einige Python-Daten aus, bevor er Ihr Skript aufruft, wie auch andere Interpreter wie ipython.

Sie können den Stack über Methoden im inspect Modul überprüfen.

In CPython für mich:

>>>print(len(inspect.stack())) 
1 

In IPython für mich:

>>>print(len(inspect.stack())) 
10 

Wie knbk darauf so schnell in den Kommentaren, wie Sie den Stapel Limit erreicht eine RecursionError geworfen wird und der Dolmetscher Erhöht das Stack-Limit ein wenig, um Ihnen die Möglichkeit zu geben, den Fehler elegant zu behandeln. Wenn Sie auch dieses Limit ausschöpfen, wird Python abstürzen.

+1

Großartiges Zeug. Ich bekomme in der Ipython-Konsole einen Stack von 23. Aber sollte dann nicht 'len (inspect.stack()) + f (0) == sys.getrecursionlimit()'? Weil ich immernoch 7 stack items kurz habe ... :-) – Aguy

+0

Was passiert wenn man 'inspect.stack() 'wenn man die Ausnahme ausgibt? – syntonym

+0

Wenn ich (inspect.stack()) innerhalb der Ausnahme drucke, bekomme ich 994. Nah genug, um die 7 Stack-Elemente zu vermissen (meine Funktion kann ein +1 Problem haben, das ich nicht vollständig aussortiert habe). Aber warum wird dann eine Ausnahme gemacht? der Stack ist nur 994 voll ... – Aguy

8

Dieses Limit gilt für den Stack, nicht für die von Ihnen definierte Funktion. Es gibt andere interne Dinge, die etwas zum Stapeln bringen könnten.

Und natürlich könnte es auf env abhängen, in dem es ausgeführt wurde. Manche können mehr stauben, manche weniger.

5

Ich glaube, dass die Verwirrung von der Differenz zwischen der Stapelgröße, die Sie sehen, wenn der Fehler auftritt, und dem Limit stammt. Die Sache ist, dass der letzte Aufruf, der den Absturz verursacht hat, wahrscheinlich für mehr als 1 Frame auf dem Stack verantwortlich ist, da er selbst einige Funktionsaufrufe durchführt. Und wenn Sie die Ausnahme abfangen, werden der Anruf und seine internen Anrufe bereits aus dem Stapel entfernt. Trotzdem können Sie sie im Traceback sehen. Schauen wir uns das an.

In [1]: import inspect 

In [2]: import sys 

In [3]: sys.setrecursionlimit(50) # I'm setting this to 50 to make the traceback shorter. 

In [4]: stack_log = [] 

In [5]: def recur(): 
    stack_log.append(len(inspect.stack())) 
    recur() 
    ...:  

In [6]: recur() 

Wir bekommen die Rückverfolgung (Note: Es gibt keine Notwendigkeit, es jetzt zu lesen, so bewegen sich nur gespannt auf den nächsten Abschnitt).

--------------------------------------------------------------------------- 
RecursionError       Traceback (most recent call last) 
<ipython-input-6-45136123341b> in <module>() 
----> 1 recur() 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
----> 2  stack_log.append(len(inspect.stack())) 
     3  recur() 
     4 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in stack(context) 
    1462 def stack(context=1): 
    1463  """Return a list of records for the stack above the caller's frame.""" 
-> 1464  return getouterframes(sys._getframe(1), context) 
    1465 
    1466 def trace(context=1): 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in getouterframes(frame, context) 
    1439  framelist = [] 
    1440  while frame: 
-> 1441   frameinfo = (frame,) + getframeinfo(frame, context) 
    1442   framelist.append(FrameInfo(*frameinfo)) 
    1443   frame = frame.f_back 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in getframeinfo(frame, context) 
    1412   start = lineno - 1 - context//2 
    1413   try: 
-> 1414    lines, lnum = findsource(frame) 
    1415   except OSError: 
    1416    lines = index = None 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in findsource(object) 
    742  is raised if the source code cannot be retrieved.""" 
    743 
--> 744  file = getsourcefile(object) 
    745  if file: 
    746   # Invalidate cache if needed. 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in getsourcefile(object) 
    670   return filename 
    671  # only return a non-existent filename if the module has a PEP 302 loader 
--> 672  if getattr(getmodule(object, filename), '__loader__', None) is not None: 
    673   return filename 
    674  # or it is in the linecache 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in getmodule(object, _filename) 
    699  # Try the cache again with the absolute file name 
    700  try: 
--> 701   file = getabsfile(object, _filename) 
    702  except TypeError: 
    703   return None 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in getabsfile(object, _filename) 
    683  if _filename is None: 
    684   _filename = getsourcefile(object) or getfile(object) 
--> 685  return os.path.normcase(os.path.abspath(_filename)) 
    686 
    687 modulesbyfile = {} 

/Users/ilia/.venvs/py3/bin/../lib/python3.5/posixpath.py in abspath(path) 
    355 def abspath(path): 
    356  """Return an absolute path.""" 
--> 357  if not isabs(path): 
    358   if isinstance(path, bytes): 
    359    cwd = os.getcwdb() 

/Users/ilia/.venvs/py3/bin/../lib/python3.5/posixpath.py in isabs(s) 
    61 def isabs(s): 
    62  """Test whether a path is absolute""" 
---> 63  sep = _get_sep(s) 
    64  return s.startswith(sep) 
    65 

RecursionError: maximum recursion depth exceeded 

Was ist mit dem Stapelprotokoll?

In [7]: stack_log[-1] 
Out[7]: 39 

Okay, wir haben 11 fehlende Frames. Scrollen Sie jetzt den Traceback zum letzten recur Aufruf, d. H.

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
     2  stack_log.append(len(inspect.stack())) 
----> 3  recur() 
     4 

<ipython-input-5-643b16f38b2e> in recur() 
     1 def recur(): 
----> 2  stack_log.append(len(inspect.stack())) 
     3  recur() 
     4 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in stack(context) 
    1462 def stack(context=1): 
    1463  """Return a list of records for the stack above the caller's frame.""" 
-> 1464  return getouterframes(sys._getframe(1), context) 
    1465 
    1466 def trace(context=1): 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in getouterframes(frame, context) 
    1439  framelist = [] 
    1440  while frame: 
-> 1441   frameinfo = (frame,) + getframeinfo(frame, context) 
    1442   framelist.append(FrameInfo(*frameinfo)) 
    1443   frame = frame.f_back 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in getframeinfo(frame, context) 
    1412   start = lineno - 1 - context//2 
    1413   try: 
-> 1414    lines, lnum = findsource(frame) 
    1415   except OSError: 
    1416    lines = index = None 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in findsource(object) 
    742  is raised if the source code cannot be retrieved.""" 
    743 
--> 744  file = getsourcefile(object) 
    745  if file: 
    746   # Invalidate cache if needed. 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in getsourcefile(object) 
    670   return filename 
    671  # only return a non-existent filename if the module has a PEP 302 loader 
--> 672  if getattr(getmodule(object, filename), '__loader__', None) is not None: 
    673   return filename 
    674  # or it is in the linecache 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in getmodule(object, _filename) 
    699  # Try the cache again with the absolute file name 
    700  try: 
--> 701   file = getabsfile(object, _filename) 
    702  except TypeError: 
    703   return None 

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py in getabsfile(object, _filename) 
    683  if _filename is None: 
    684   _filename = getsourcefile(object) or getfile(object) 
--> 685  return os.path.normcase(os.path.abspath(_filename)) 
    686 
    687 modulesbyfile = {} 

/Users/ilia/.venvs/py3/bin/../lib/python3.5/posixpath.py in abspath(path) 
    355 def abspath(path): 
    356  """Return an absolute path.""" 
--> 357  if not isabs(path): 
    358   if isinstance(path, bytes): 
    359    cwd = os.getcwdb() 

/Users/ilia/.venvs/py3/bin/../lib/python3.5/posixpath.py in isabs(s) 
    61 def isabs(s): 
    62  """Test whether a path is absolute""" 
---> 63  sep = _get_sep(s) 
    64  return s.startswith(sep) 
    65 

RecursionError: maximum recursion depth exceeded 

Und hier sind, gibt es genau 11 Funktionsaufrufe (die Pfeile auf der linken Seite), dass 11 Frames auf dem Stapel befindet, die entfernt wurden, wenn die Exception ausgelöst wurde.