繁体   English   中英

如何获取 Python 解释器堆栈的当前深度?

[英]How do I get the current depth of the Python interpreter stack?

文档

sys.getrecursionlimit()

返回递归限制的当前值,Python解释器堆栈的最大深度。 此限制可防止无限递归导致 C 堆栈溢出和 Python 崩溃。 可以通过 setrecursionlimit() 设置。

我目前在酸洗对象时达到了递归限制。 我正在酸洗的对象只有几层嵌套,所以我对发生的事情感到有些困惑。

我已经能够通过以下黑客来规避这个问题:

try:
    return pickle.dumps(x)
except:
    try:
        recursionlimit = getrecursionlimit()
        setrecursionlimit(2*recursionlimit)
        dumped = pickle.dumps(x)
        setrecursionlimit(recursionlimit)
        return dumped
    except:
        raise

在不同的上下文中测试上述代码片段有时会导致第一次try成功,有时会导致第二次try成功。 到目前为止,我还没有能够让它raise异常。

为了进一步调试我的问题,有一种方法可以获得堆栈的当前深度会很有帮助。 这将允许我验证输入堆栈深度是否决定了上面的代码片段在第一次try还是第二次try会成功。

标准库是否提供了获取栈深度的函数,如果没有,如何获取?

def get_stack_depth():
    # what goes here?

您可以从inspect.stack()看到整个调用堆栈,因此当前采用的深度将是len(inspect.stack(0))

另一方面,我猜您在引发“超出最大递归深度”异常时打印了完整的堆栈。 该堆栈跟踪应准确显示出了什么问题。

如果速度是一个问题,绕过检查模块会更快。

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

基准100

高级算法:

正如@lunixbochs 在回答中提出的那样, sys._getframe()基本上是 C 代码中的stackdepth1() 虽然更简单的算法总是从堆栈顶部的现有帧开始在 Python 中的深度搜索向下检查堆栈以获取更多现有帧,但stacksize4b()允许通过其stack_hint参数从任何级别开始搜索,并且可以搜索堆栈如果需要,向下或向上。

sys._getframe() ,调用sys._getframe()始终意味着将堆栈从顶部帧向下移动到指定深度。 因为 Python 和 C 之间的性能差异如此巨大,如果有必要,在应用基本的近距离逐帧之前,多次调用sys._getframe()仍然可以得到回报使用frame.f_back在 Python 中搜索。

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()时序中的特征锯齿图案。

基准300 基准3000

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM