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装饰器的意义。他们介绍了