簡體   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