2016-07-08 32 views
27

Ho fatto una piccola funzione che effettivamente misurare il limite massimo di ricorsione:La ricorsione massima non è esattamente ciò che afferma sys.getrecursionlimit(). Come mai?

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

di sapere cosa aspettarsi Ho controllato:

In [28]: import sys 

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

Tuttavia

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

Il numero non è fisso, sempre attorno a ~ 970, e cambia leggermente tra le diverse istanze di python (ad esempio da spyder al prompt di sistema cmd).

Si noti che sto usando ipython su python3.

Cosa sta succedendo? Perché il limite effettivo è inferiore al valore sys.getrecursionlimit()?

+0

È una protezione contro un eccesso di stack. Puoi cambiare il limite di ricorsione con 'sys.setrecursionlimit', ma farlo è pericoloso. – dazzieta

+1

Cosa succede quando imposti manualmente il ricorsionlimit usando 'sys.setrecursionlimit (limit)' (https://docs.python.org/3/library/sys.html#sys.setrecursionlimit) all'inizio del tuo codice? Vedi anche http://stackoverflow.com/questions/3323001/maximum-recursion-depth e http://stackoverflow.com/questions/5061582/setting-stacksize-in-a-python-script/16248113#16248113 – linusg

+2

Solo un nota a margine. Non devi correggere il tuo codice ricorsivo aumentando il limite di ricorsione, perché non è a prova di carico. Se vuoi davvero una ricorsione, usa TCO e un decoratore per eliminare le chiamate di coda (ce ne sono molte). O semplicemente attenersi a un'alternativa imperativa. –

risposta

35

Il limite di ricorsione non è il limite di ricorsione ma la profondità massima dello stack di interprete python. C'è qualcosa in pila prima che la funzione venga eseguita. Spyder esegue alcune cose di Python prima di chiamare il tuo script, così come altri interpreti come ipython.

È possibile ispezionare lo stack tramite i metodi nel modulo inspect.

In CPython per me:

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

In ipython per me:

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

Come knbk sottolineato nei commenti, non appena si preme il limite dello stack un RecursionError è gettato e l'interprete aumenta leggermente il limite dello stack per darti la possibilità di gestire l'errore con garbo. Se si esaurisce anche quel limite, python si bloccherà.

+1

Grandi cose. Ottengo una console di Ipython di uno stack di 23. Ma non dovrei quindi 'len (inspect.stack()) + f (0) == sys.getrecursionlimit()'? Perché io sono ancora 7 oggetti in pila ... :-) – Aguy

+0

Cosa succede se si stampa (inspect.stack()) 'quando si eccetto l'eccezione? – syntonym

+0

Se ho 'print (inspect.stack())' all'interno dell'eccezione ottengo 994. Abbastanza vicino a 7 elementi mancanti (la mia funzione potrebbe avere un problema +1 che non ho eliminato completamente). Ma allora perché viene sollevata un'eccezione? lo stack è solo 994 completo ... – Aguy

8

Questo limite è per stack, non per la funzione definita dall'utente. Ci sono altre cose interne che potrebbero spingere qualcosa da impilare.

E ovviamente potrebbe dipendere da env in cui è stato eseguito. Alcuni possono inquinare più stack, altri meno.

5

Credo che la confusione derivi dalla differenza tra la dimensione dello stack che si vede quando si verifica l'errore e il limite. Il fatto è che l'ultima chiamata, che ha causato l'arresto anomalo, probabilmente rappresenta più di 1 frame nello stack, perché fa alcune chiamate di funzione. E nel momento in cui rilevi l'eccezione, la chiamata e le sue chiamate interne vengono già rimosse dallo stack. Tuttavia, è possibile vederli nel traceback. Diamo un'occhiata a questo.

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

otteniamo il traceback (nota: non c'è bisogno di leggere ora, quindi basta andare avanti alla sezione successiva).

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

Cosa c'è nel registro stack?

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

Ok, abbiamo 11 frame mancanti. Ora, scorrere il traceback fino all'ultima chiamata recur, ad es.

<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 

Ed eccoti qui, ci sono esattamente 11 chiamate di funzione (le frecce a sinistra), che è di 11 fotogrammi sullo stack che sono stati rimossi quando l'eccezione è stata sollevata.