我做了一个实际测量最大递归限制的小函数:
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会崩溃.
我相信混淆源于您在发生错误时看到的堆栈大小与限制之间的差异.问题在于导致崩溃的最后一次调用可能在堆栈上占据了超过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个帧.