最大递归并不是sys.getrecursionlimit()所声称的.怎么会?

Agu*_*guy 27 python recursion

我做了一个实际测量最大递归限制的小函数:

def f(x):
    r = x
    try:
        r = f(x+1)
    except Exception as e:
        print(e)
    finally:
        return r
Run Code Online (Sandbox Code Playgroud)

知道我期待什么我检查过:

In [28]: import sys

In [29]: sys.getrecursionlimit()
Out[29]: 1000
Run Code Online (Sandbox Code Playgroud)

然而

In [30]: f(0)
maximum recursion depth exceeded
Out[30]: 970
Run Code Online (Sandbox Code Playgroud)

数字不固定,总是在~970左右,并且在python的不同实例之间略有变化(例如从spyder到system cmd提示符).

请注意我在python3上使用ipython.

这是怎么回事?为什么实际限制我会低于sys.getrecursionlimit()价值?

syn*_*nym 35

递归限制不是递归的限制,而是python解释器堆栈的最大深度.在执行函数之前,堆栈上有一些东西.Spyder在调用脚本之前会执行一些python内容,就像ipython等其他解释器一样.

您可以通过inspect模块中的方法检查堆栈.

在CPython中对我来说:

>>>print(len(inspect.stack()))
1
Run Code Online (Sandbox Code Playgroud)

在Ipython中对我来说:

>>>print(len(inspect.stack()))
10
Run Code Online (Sandbox Code Playgroud)

正如knbk在注释中指出的那样,一旦达到堆栈限制,就会抛出RecursionError,并且解释器会稍微提高堆栈限制,以便您可以优雅地处理错误.如果你也耗尽了那个限制python会崩溃.

  • @Falco实际上(至少在CPython中),达到堆栈限制将设置一个标志并暂时提高堆栈限制(50,IIRC)以允许干净地处理异常.如果在处理异常时达到新的堆栈限制,则会发生致命错误,并且您的应用程序将崩溃.在第一个非临时限制下降50或更多将重置标志和限制,再次到达它将创建一个新的异常而不是致命的错误.所以不,对'print(e)`的调用不会失败. (6认同)

Via*_*iuk 8

此限制适用于堆栈,而不适用于您定义的功能.还有其他内部事物可能会推动堆叠.

当然,它可能取决于执行它的环境.有些可能会污染堆栈更多,更少.


Eli*_*igo 5

我相信混淆源于您在发生错误时看到的堆栈大小与限制之间的差异.问题在于导致崩溃的最后一次调用可能在堆栈上占据了超过1帧,因为它本身会进行一些函数调用.当您捕获异常时,调用及其内部调用已从堆栈中删除.不过,你可以在追溯中看到它们.我们来看看这一个.

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()
Run Code Online (Sandbox Code Playgroud)

我们得到回溯(注意:现在没有必要阅读它,所以只需前进到下一部分).

---------------------------------------------------------------------------
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
Run Code Online (Sandbox Code Playgroud)

堆栈日志是什么?

In [7]: stack_log[-1]
Out[7]: 39
Run Code Online (Sandbox Code Playgroud)

好的,我们有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'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
Run Code Online (Sandbox Code Playgroud)

在这里,您确实有11个函数调用(左侧的箭头),即在引发异常时删除的堆栈中的11个帧.