简体   繁体   English

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

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

From the documentation :文档

sys.getrecursionlimit()

Return the current value of the recursion limit, the maximum depth of the Python interpreter stack.返回递归限制的当前值,Python解释器堆栈的最大深度。 This limit prevents infinite recursion from causing an overflow of the C stack and crashing Python.此限制可防止无限递归导致 C 堆栈溢出和 Python 崩溃。 It can be set by setrecursionlimit().可以通过 setrecursionlimit() 设置。

I am currently hitting the recursion limit when pickling an object.我目前在酸洗对象时达到了递归限制。 The object I am pickling only has a few levels of nesting, so I am a bit puzzled by what is happening.我正在酸洗的对象只有几层嵌套,所以我对发生的事情感到有些困惑。

I have been able to circumvent the issue with the following hack:我已经能够通过以下黑客来规避这个问题:

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

Testing the above snippet on different contexts sometimes leads to success on the first try , and sometimes it leads to success on the second try .在不同的上下文中测试上述代码片段有时会导致第一次try成功,有时会导致第二次try成功。 So far I have not been able to make it raise the exception.到目前为止,我还没有能够让它raise异常。

To further debug my issue it would be helpful to have a way to obtain the current depth of the stack.为了进一步调试我的问题,有一种方法可以获得堆栈的当前深度会很有帮助。 That would allow me to verify if the entering stack depth is determining whether the snippet above will succeed on the first try or on the second.这将允许我验证输入堆栈深度是否决定了上面的代码片段在第一次try还是第二次try会成功。

Does the standard library provide a function to get the depth of the stack, or if not, how can I obtain it?标准库是否提供了获取栈深度的函数,如果没有,如何获取?

def get_stack_depth():
    # what goes here?

You can see the whole call stack from inspect.stack() , so currently taken depth would be len(inspect.stack(0)) .您可以从inspect.stack()看到整个调用堆栈,因此当前采用的深度将是len(inspect.stack(0))

On the other hand, I guess you got the complete stack printed out when "maximum recursion depth exceeded" exception was raised.另一方面,我猜您在引发“超出最大递归深度”异常时打印了完整的堆栈。 That stack trace should show you exactly what went wrong.该堆栈跟踪应准确显示出了什么问题。

If speed is an issue, it's way faster to bypass inspect module.如果速度是一个问题,绕过检查模块会更快。

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

I shortened the name of my functions to stacksize() and for easier differentiation, I'm referring to @lunixbochs' functions as stackdepth() .我将我的函数名称缩短为stacksize()并且为了更容易区分,我将@lunixbochs 的函数称为stackdepth()


Basic Algorithms:基本算法:

That's probably the best compromise between code brevity, readability and speed for small stack sizes.对于小堆栈大小,这可能是代码简洁性、可读性和速度之间的最佳折衷。 For under ~10 frames, only stackdepth1() is slightly faster due to lower overhead.对于不到 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

For achieving better timings for larger stack sizes, some more refined algorithms are possible.为了为更大的堆栈大小实现更好的时序,一些更精细的算法是可能的。 stacksize3a() is combining chained attribute lookup with a close range finish from stackdepth1() for a much more favorable slope in timings, starting to pay off for roughly > 70 frames in my benchmarks. 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

Advanced Algorithms:高级算法:

As @lunixbochs has brought up in an answer, sys._getframe() is basically stackdepth1() in C-code.正如@lunixbochs 在回答中提出的那样, sys._getframe()基本上是 C 代码中的stackdepth1() While simpler algorithms always start their depth-search in Python from an existing frame at the top of stack, checking the stack downward for further existing frames, stacksize4b() allows starting the search from any level by its stack_hint -parameter and can search the stack down- or upward if needed.虽然更简单的算法总是从堆栈顶部的现有帧开始在 Python 中的深度搜索向下检查堆栈以获取更多现有帧,但stacksize4b()允许通过其stack_hint参数从任何级别开始搜索,并且可以搜索堆栈如果需要,向下或向上。

Under the hood, calling sys._getframe() always means walking the stack from the top frame downward to a specified depth.sys._getframe() ,调用sys._getframe()始终意味着将堆栈从顶部帧向下移动到指定深度。 Because the performance difference between Python and C is so huge, it can still pay off to call sys._getframe() multiple times if necessary to find a frame closer to the deepest one, before applying a basic close-range frame-by-frame search in Python with frame.f_back .因为 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

The usage-idea of stacksize4b() is to place the size-hint at the lower bound of your expected stack depth for a jump start, while still being able to cope with every drastic and short-lived change in stack-depth. stacksize4b()的使用思路是将大小提示置于预期堆栈深度的下限,以便快速启动,同时仍然能够应对堆栈深度的每一次剧烈和短暂的变化。

The benchmark shows stacksize4b() with default size_hint=8 and adjusted size_hint=200 .基准测试显示stacksize4b()与默认size_hint=8和调整size_hint=200 For the benchmark all stack depths in the range 3-3000 have been tested to show the characteristic saw pattern in timings for stacksize4b() .对于基准测试,3-3000 范围内的所有堆栈深度都经过测试,以显示stacksize4b()时序中的特征锯齿图案。

基准300 基准3000

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

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