如何获取 Python 解释器堆栈的当前深度?
Posted
技术标签:
【中文标题】如何获取 Python 解释器堆栈的当前深度?【英文标题】:How do I get the current depth of the Python interpreter stack? 【发布时间】:2016-03-10 23:27:08 【问题描述】:来自documentation:
sys.getrecursionlimit()
返回递归限制的当前值,Python 解释器堆栈的最大深度。此限制可防止无限递归 避免导致 C 堆栈溢出和 Python 崩溃。有可能 由 setrecursionlimit() 设置。
我目前在酸洗对象时达到了递归限制。我正在腌制的对象只有几层嵌套,所以我对正在发生的事情感到有些困惑。
我已经能够通过以下 hack 规避这个问题:
try:
return pickle.dumps(x)
except:
try:
recursionlimit = getrecursionlimit()
setrecursionlimit(2*recursionlimit)
dumped = pickle.dumps(x)
setrecursionlimit(recursionlimit)
return dumped
except:
raise
在不同的上下文中测试上述 sn-p 有时会导致第一个 try
成功,有时它会导致第二个 try
成功。到目前为止,我还无法将raise
设为例外。
为了进一步调试我的问题,有一种方法可以获得堆栈的当前深度会很有帮助。这将允许我验证输入堆栈深度是否确定上面的 sn-p 是否会在第一个 try
或第二个上成功。
标准库是否提供了获取堆栈深度的函数,如果没有,如何获取?
def get_stack_depth():
# what goes here?
【问题讨论】:
这听起来像是一个 XY 问题。而不是弄清楚如何绕过递归限制,您应该尝试弄清楚为什么要达到它。 @IanAuld 确实如此。这正是我想要做的,我试图看看问题是否取决于我进行有问题的调用时的堆栈深度。为此,我需要弄清楚如何获取堆栈的当前深度。 如果你能提供你在获取/设置限制之间所做的事情会更好......至少是一个原型版本。这样我们就可以破解它...x
的值是多少?
【参考方案1】:
如果速度是个问题,绕过检查模块会更快。
testing depth: 50 (CPython 3.7.3)
stacksize4b() | depth: 50 | 2.0 µs
stacksize4b(200) | depth: 50 | 2.2 µs
stacksize3a() | depth: 50 | 2.4 µs
stacksize2a() | depth: 50 | 2.9 µs
stackdepth2() | depth: 50 | 3.0 µs
stackdepth1() | depth: 50 | 3.0 µs
stackdepth3() | depth: 50 | 3.4 µs
stacksize1() | depth: 50 | 7.4 µs # deprecated
len(inspect.stack()) | depth: 50 | 1.9 ms
我将我的函数名称缩短为stacksize()
,为了便于区分,我将@lunixbochs 的函数称为stackdepth()
。
基本算法:
对于小堆栈大小,这可能是代码简洁性、可读性和速度之间的最佳折衷。对于大约 10 帧以下,只有 stackdepth1()
稍快一些,因为开销较低。
from itertools import count
def stack_size2a(size=2):
"""Get stack size for caller's frame.
"""
frame = sys._getframe(size)
for size in count(size):
frame = frame.f_back
if not frame:
return size
为了为更大的堆栈大小实现更好的时序,可以使用一些更精细的算法。
stacksize3a()
将链式属性查找与stackdepth1()
的近距离完成相结合,以获得更有利的时序斜率,在我的基准测试中开始获得大约 70 帧以上的回报。
from itertools import count
def stack_size3a(size=2):
"""Get stack size for caller's frame.
"""
frame = sys._getframe(size)
try:
for size in count(size, 8):
frame = frame.f_back.f_back.f_back.f_back.\
f_back.f_back.f_back.f_back
except AttributeError:
while frame:
frame = frame.f_back
size += 1
return size - 1
高级算法:
正如@lunixbochs 在答案中提出的那样,sys._getframe()
在 C 代码中基本上是 stackdepth1()
。虽然更简单的算法总是从堆栈顶部的现有帧开始他们的深度搜索在 Python 中,向下检查堆栈以查找更多现有帧,stacksize4b()
允许从任何级别通过其 @ 开始搜索987654338@-parameter 并且可以根据需要向下或向上搜索堆栈。
在底层,调用sys._getframe()
总是意味着将堆栈从顶部框架向下移动到指定深度。由于 Python 和 C 之间的性能差异如此之大,因此在应用基本的近距离逐帧搜索之前,如果需要找到更接近最深的帧,仍然可以多次调用 sys._getframe()
Python 与 frame.f_back
.
from itertools import count
def stack_size4b(size_hint=8):
"""Get stack size for caller's frame.
"""
get_frame = sys._getframe
frame = None
try:
while True:
frame = get_frame(size_hint)
size_hint *= 2
except ValueError:
if frame:
size_hint //= 2
else:
while not frame:
size_hint = max(2, size_hint // 2)
try:
frame = get_frame(size_hint)
except ValueError:
continue
for size in count(size_hint):
frame = frame.f_back
if not frame:
return size
stacksize4b()
的使用理念是将大小提示置于预期堆栈深度的下限,以便快速开始,同时仍能够应对堆栈深度的每一次剧烈和短暂的变化.
基准测试显示stacksize4b()
和默认size_hint=8
和调整后的size_hint=200
。对于基准测试,3-3000 范围内的所有堆栈深度都经过测试,以显示 stacksize4b()
时序中的特征锯齿图案。
【讨论】:
@lunixbochs 该算法的使用理念是将大小提示放置在预期堆栈深度的下限处,以便快速开始,同时仍然能够应对每一次剧烈和短暂的堆栈深度的实时变化。 下限而不是中间,因为触发的异常相对昂贵,所以算法的反向搜索应该是有限的。 @lunixbochs 如果你有一个更大的应用程序,它基本上不会低于某个堆栈深度,并且你有充分的理由首先考虑速度而不是代码简洁性,那么这是最有意义的,比如您经常尝试记录调用者(Python 的动态特性使得这对于诸如静态方法之类的事情非常困难)、堆栈深度本身或类似的事情。 @lunixbochs 是的,听起来是有原因的。这取决于预期的堆栈大小。如果您的平均帧数不超过约 20 帧,那么坚持您的速度答案应该没问题。 哇,这是一个了不起的答案。感谢您深入了解细节并展示所有漂亮的图表。【参考方案2】:您可以从inspect.stack()
看到整个调用堆栈,因此当前采用的深度将是len(inspect.stack(0))
。
另一方面,我猜当 “超出最大递归深度” 异常被引发时,您会打印出完整的堆栈。该堆栈跟踪应该准确地告诉您出了什么问题。
【讨论】:
以上是关于如何获取 Python 解释器堆栈的当前深度?的主要内容,如果未能解决你的问题,请参考以下文章
如何在python中获取当前地理位置NO IP LOCATION?
如何在Dart中获取当前的堆栈跟踪以获得Completer.CompleteException(exception,stackTrace);