Подтвердить что ты не робот

Макс рекурсия не совсем то, что sys.getrecursionlimit() утверждает. Как так?

Я сделал небольшую функцию, которая будет фактически измерять предел максимальной рекурсии:

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

Чтобы узнать, чего ожидать, я проверил:

In [28]: import sys

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

Однако

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

Число не фиксировано, всегда около ~ 970 и немного изменяется между разными экземплярами python (например, из командной строки spyder в system cmd).

Обратите внимание, что я использую ipython на python3.

Что происходит? Почему фактический предел я становлюсь ниже значения sys.getrecursionlimit()?

4b9b3361

Ответ 1

Предел рекурсии не является пределом для рекурсии, но максимальная глубина стека интерпретатора python. Перед выполнением вашей функции есть что-то в стеке. Spyder выполняет некоторые вещи python, прежде чем он называет ваш script, как и другие интерпретаторы, такие как ipython.

Вы можете проверить стек через методы в модуле inspect.

В CPython для меня:

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

В Ipython для меня:

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

Как отметил knbk в комментариях, как только вы нажмете на лимит стека, вызывается RecursionError, и интерпретатор немного увеличивает лимит стека, чтобы дать вам возможность обработать ошибку изящно. Если вы также исчерпаете этот предел, python сработает.

Ответ 2

Этот предел для стека, а не для функции, которую вы определяете. Есть и другие внутренние вещи, которые могут подтолкнуть что-то к стеке.

И, конечно, это может зависеть от env, в котором он был выполнен. Некоторые могут загрязнять стек больше, а некоторые меньше.

Ответ 3

Я считаю, что путаница возникает из-за разницы между размером стека, который вы видите, когда возникает ошибка, и предел. Дело в том, что последний вызов, вызвавший сбой, скорее всего, содержит более одного кадра в стеке, поскольку сам он вызывает некоторые вызовы функций. И к тому времени, когда вы поймаете исключение, вызов и его внутренние вызовы уже удалены из стека. Тем не менее, вы можете увидеть их в трассировке. Давайте посмотрим на это.

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

Мы получаем traceback ( note: нет необходимости читать его сейчас, поэтому просто перейдите к следующему разделу).

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

Что с журналом стека?

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

Хорошо, у нас есть 11 недостающих кадров. Теперь прокрутите трассировку до последнего вызова 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 

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

И вот вы, есть ровно 11 вызовов функций (стрелки слева), то есть 11 кадров в стеке, которые были удалены при возникновении исключения.