如何获取 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 解释器堆栈的当前深度?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 中获取当前堆栈跟踪?

如何在 Java 中获取当前堆栈跟踪?

如何在python中获取当前地理位置NO IP LOCATION?

如何在Dart中获取当前的堆栈跟踪以获得Completer.CompleteException(exception,stackTrace);

如何在python中获取嵌套异常的堆栈跟踪?

你如何在 Java 中获取线程堆栈?