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

时间:2015-12-06 08:00:23

标签: python stack interpreter

来自documentation

  

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或第二个def get_stack_depth(): # what goes here? 上成功。

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

box-shadow

3 个答案:

答案 0 :(得分:12)

您可以从inspect.stack()看到整个调用堆栈,因此当前获取的深度为len(inspect.stack())

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

答案 1 :(得分:6)

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

def get_stack_size():
    """Get stack size for caller's frame.

    %timeit len(inspect.stack())
    8.86 ms ± 42.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    %timeit get_stack_size()
    4.17 µs ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    """
    size = 2  # current frame and caller's frame always exist
    while True:
        try:
            sys._getframe(size)
            size += 1
        except ValueError:
            return size - 1  # subtract current frame

答案 2 :(得分:0)

我经历了几次迭代,stack2()是我最喜欢的简单性和速度。最后有一些基准。

stack2()-范围迭代器

事实证明,从stack1()开始的整数增量相当昂贵,但是我相信range是C内置的,因此我们可以通过使用它来跳过一些字节码指令。由于我们不在乎帧用完时抛出的ValueError,因此try: finally: return是抑制它并返回的简单方法。

def stack2() -> int:
    frame = sys._getframe(1)
    i = 0
    try:
        for i in range(0xffffffff):
            frame = frame.f_back
    finally:
        return i

stack1()-原始尝试

这比达克诺特的原始答案要快。重复的sys._getframe(n)是二次时间(因为sys._getframe()基本上是该函数的C实现)。

在调查len(inspect.stack())为何这么慢的原因时,我注意到inspect.getouterframes()循环引用了frame.f_back,这就是我的灵感。

def stack1():
    frame = sys._getframe(1)
    i = 0
    while frame:
        frame = frame.f_back
        i += 1
    return i

stack3()-基于Darkonaut的get_stack_size()但比较讨人喜欢

这是尝试使Darkonaut的第二个版本的控制流程扁平化。我对此有不同的感觉,它在高堆栈大小时速度很快,但是除非您提高递归限制,否则我不会为stack2()所困扰。

def stack3():
    get_frame = sys._getframe
    try:
        i = 0
        for i in range(64):
            frame = get_frame(1 << i)
    except ValueError:
        n = 1 << (i - 1)
    try:
        for n in range(n - 1, n << 1):
            frame = frame.f_back
    finally:
        return n

C扩展名

我仍在考虑切换到C版本的项目中的权衡。我认为,如果我对这行进行剖析并且stack_size()函数显示得足够多,我会做的。此功能的主要缺点是构建/分发自定义扩展,并且它不适用于pypy之类的东西(在这种情况下,朴素的python版本在插入时可能比cpyext兼容层更快)

static PyObject *stack_size(PyObject *self, PyObject *args) {
    PyFrameObject *frame = PyEval_GetFrame();
    int i = 0;
    while (frame) {
        frame = frame->f_back;
        i++;
    }
    return PyLong_FromLong(i);
}

小的堆栈深度基准

Small stack depth benchmark

大堆栈深度基准

Large stack depth benchmark