Python 最大递归不是sys.getrecursionlimit()所声称的。怎么会?
我制作了一个小函数,它将实际测量最大递归限制:Python 最大递归不是sys.getrecursionlimit()所声称的。怎么会?,python,recursion,Python,Recursion,我制作了一个小函数,它将实际测量最大递归限制: 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) max
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 prompt)
请注意,我正在python3上使用ipython
发生什么事了为什么我得到的实际限制低于
sys.getrecursionlimit()
值?递归限制不是递归限制,而是python解释器堆栈的最大深度。在执行函数之前,堆栈上有一些东西。Spyder在调用您的脚本之前执行一些python内容,其他解释器(如ipython)也是如此
您可以通过inspect
模块中的方法检查堆栈
在我的CPython中:
>>>print(len(inspect.stack()))
1
>>>print(len(inspect.stack()))
10
在我的Ipython中:
>>>print(len(inspect.stack()))
1
>>>print(len(inspect.stack()))
10
正如knbk在评论中指出的,一旦达到堆栈限制,就会抛出一个递归错误,解释器会稍微提高堆栈限制,使您能够优雅地处理错误。如果您也耗尽了这个限制,python将崩溃。这个限制是针对堆栈的,而不是针对您定义的函数。还有其他内部事物可能会将某些东西推到堆栈上
当然,它可能取决于执行它的环境。有些会对堆栈造成更多的污染,有些则会造成更少的污染。我认为这种混淆源于错误发生时看到的堆栈大小与限制之间的差异。问题是,导致崩溃的最后一个调用可能会占用堆栈上的多个帧,因为它本身会进行一些函数调用。当您捕获异常时,调用及其内部调用将从堆栈中删除。你可以在回溯中看到它们。让我们看看这个
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()
我们得到了回溯(注意:现在没有必要阅读它,所以只需前进到下一节)
好的,我们有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
recur()中的
1 def recur():
2 stack_log.append(len(inspect.stack()))
---->3再次发生()
4.
在repur()中
1 def recur():
---->2 stack_log.append(len(inspect.stack()))
3再次发生()
4.
/堆栈中的Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py(上下文)
1462 def堆栈(上下文=1):
1463“返回调用方帧上方堆栈的记录列表。”“”
->1464返回GetOuterFrame(系统\ getframe(1),上下文)
1465
1466 def跟踪(上下文=1):
/getouterframes(框架,上下文)中的Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py
1439框架清单=[]
1440而帧:
->1441帧信息=(帧,)+getframeinfo(帧,上下文)
1442框架列表。追加(框架信息(*框架信息))
1443帧=帧f_后
/getframeinfo(框架,上下文)中的Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py
1412 start=lineno-1-context//2
1413尝试:
->1414行,lnum=findsource(帧)
1415除操作错误外:
1416行=索引=无
/findsource(对象)中的Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py
如果无法检索源代码,则引发742。”“”
743
-->744文件=getsourcefile(对象)
745如果文件:
746#如果需要,使缓存无效。
/getsourcefile(对象)中的Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py
670返回文件名
671#如果模块具有PEP 302加载程序,则仅返回不存在的文件名
-->672如果getattr(getmodule(对象,文件名),“\uuuuu loader\uuuuuu”,None)不是None:
673返回文件名
674#或者它在缓存中
/getmodule(对象,_文件名)中的Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py
699#使用绝对文件名重试缓存
700尝试:
-->701 file=getabsfile(对象,文件名)
702除类型错误外:
703无返回
/getabsfile(object,_filename)中的Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/inspect.py
683如果_filename为None:
684 _filename=getsourcefile(对象)或getfile(对象)
-->685返回os.path.normcase(os.path.abspath(_文件名))
686
687 modulesbyfile={}
/abspath(path)中的Users/ilia/.venvs/py3/bin/./lib/python3.5/posixpath.py
355 def ABS路径(路径):
356“返回绝对路径”
-->357如果不是ISBS(路径):
358如果isinstance(路径,字节):
359 cwd=os.getcwdb()
/ISBS中的Users/ilia/.venvs/py3/bin/./lib/python3.5/posixpath.py
61名雇员:
62“测试路径是否为绝对路径”
--->9月63日=获得9月
64返回s.startswith(九月)
65
递归错误:超过最大递归深度
这里有11个函数调用(左边的箭头),即在引发异常时已删除的堆栈上的11个帧。这是一种防止堆栈溢出的措施。您可以使用
sys.setrecursionlimit
更改递归限制,但这样做是危险的。如果使用sys.setrecursionlimit(limit)
()在代码的开头?另请参阅,并只是一个旁注。您不应该通过提高递归限制来修复递归代码,因为这不是负载保护。如果您确实想要递归,那么使用TCO和装饰器来消除尾部调用(有很多)。或者只是坚持一个强制性的替代方案。@utkarsh13-在你之前刚刚写到:)@EliKorvigo我真的不明白使用tco装饰器的意义。他们介绍了