2016-07-08 16 views
27

Zrobiłem małą funkcję, która będzie rzeczywiście mierzyć limitu max rekurencji:Maksymalna rekurencja nie jest dokładnie tym, co roszczenia sys.getrecursionlimit(). Dlaczego?

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

wiedzieć, czego się spodziewać Sprawdziłem:

In [28]: import sys 

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

Jednak

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

Liczba nie jest ustalona, ​​zawsze około ~ 970, i nieznacznie zmienia się pomiędzy różnymi instancjami pythona (np. Od wewnątrz spydera do polecenia systemowego cmd).

Należy pamiętać, że używam ipython na python3.

Co się dzieje? Dlaczego aktualny limit jest niższy niż wartość sys.getrecursionlimit()?

+0

Jest to osłona przed przepełnieniem stosu. Możesz zmienić limit rekursji za pomocą 'sys.setrecursionlimit', ale jest to niebezpieczne. – dazzieta

+1

Co stanie się, gdy ręcznie ustawisz limit rekursji używając 'sys.setrecursionlimit (limit)' (https://docs.python.org/3/library/sys.html#sys.setrecursionlimit) na początku kodu? Zobacz także http://stackoverflow.com/questions/3323001/maximum-recursion-depth i http://stackoverflow.com/questions/5061582/setting-stacksize-in-a-python-script/16248113#16248113 – linusg

+2

Po prostu dygresja. Nie powinieneś naprawiać kodu rekursywnego, podnosząc limit rekursji, ponieważ nie jest on odporny na obciążenia. Jeśli naprawdę chcesz rekurencji, użyj TCO i dekoratora, aby wyeliminować ogonki (jest ich mnóstwo). Lub po prostu trzymaj się imperatywnej alternatywy. –

Odpowiedz

35

Limit rekursji nie jest limitem rekursji, ale maksymalną głębią stosu interpretera Pythona.Nie jest coś na stosie przed wykonaniem funkcji. Spyder wykonuje pewne rzeczy z pythona, zanim wywoła twój skrypt, podobnie jak inni tłumacze, tacy jak ipython.

Możesz przeglądać stos za pomocą metod w module inspect.

W CPython dla mnie:

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

W ipython dla mnie:

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

Jak knbk zauważył w komentarzach, jak tylko trafisz limitu spiętrzanie RecursionError jest wyrzucane i tłumacza nieznacznie podnosi limit stosu, aby dać ci możliwość płynnego uchwycenia błędu. Jeśli wyczerpiesz także ten limit, pyton się zawiesi.

+1

Świetne rzeczy. Dostaję w konsoli Ipython stos 23. Ale czy nie powinienem wtedy "len (inspect.stack()) + f (0) == sys.getrecursionlimit()'? Ponieważ wciąż mam 7 przedmiotów na stosie krótko ... :-) – Aguy

+0

Co się stanie, jeśli "wypiszesz (inspect.stack())", kiedy wyjdziesz poza wyjątek? – syntonym

+0

Jeśli 'print (inspect.stack())' wewnątrz wyjątku otrzymuję 994. Wystarczająco blisko do 7 elementów stosu brakuje (moja funkcja może mieć problem +1, który nie został całkowicie usunięty). Ale w takim razie, dlaczego zgłaszany jest wyjątek? stos jest tylko 994 pełny ... – Aguy

8

Ten limit dotyczy stosu, a nie zdefiniowanej funkcji. Są inne wewnętrzne rzeczy, które mogą wepchnąć coś do stosu.

Oczywiście może to zależeć od środowiska, w którym zostało wykonane. Niektóre mogą zanieczyszczać stos, a niektóre mniej.

5

Wierzę, że zamieszanie pochodzi od różnicy między rozmiarem stosu, który widzisz, gdy wystąpi błąd, a granicą. Rzecz w tym, że ostatnie wywołanie, które spowodowało awarię, prawdopodobnie stanowi więcej niż jedną ramkę na stosie, ponieważ samo powoduje pewne wywołania funkcji. W momencie wychwycenia wyjątku połączenie i wewnętrzne połączenia są już usuwane ze stosu. Niemniej jednak można je zobaczyć w systemie śledzenia. Spójrzmy na to.

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

Dostajemy traceback (notatka: Nie ma potrzeby, aby ją przeczytać teraz, tak po prostu przejść do następnej sekcji).

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

Co zawiera dziennik stosów?

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

Okay, mamy 11 brakujących ramek. Teraz przewiń w dół ścieżkę zwrotną do ostatniego wywołania recur, tj.

<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 

I tu jesteś, istnieją dokładnie 11 wywołania funkcji (strzałek po lewej), czyli 11 klatek na stosie, które zostały usunięte, gdy wyjątek został podniesiony.

Powiązane problemy