簡體   English   中英

python“nonlocal”關鍵字如何實現? 可以節省空間嗎?

[英]How does python "nonlocal" keyword implement? Can it save space?

現在我正在考慮編寫一個代碼來獲得二叉樹的最大深度。 Python 不能通過引用傳遞非容器參數,所以一般有兩種選擇,使用nonlocal關鍵字或通過復制傳遞深度。

第一個get_max_depth1需要更多的回溯操作,我想知道它是否比get_max_depth1花費更少的空間。 如果 python 實現了nonlocal使用參數通過引用傳遞,那么每個 function 還帶一個 integer 指針,在這種情況下,它不亞於get_max_depth2運行速度更慢,並且節省空間。 如果沒有的話,二叉樹深度為100000時, get_max_depth1只需要一個變量, get_max_depth2需要100000個變量d保存在他們的function中,我猜想外面寫d是有意義的。

def main():
    root = BinaryTreeNode()
    d = 0
    maxd1 = 0
    def get_max_depth1(node):
        nonlocal d,maxd1
        maxd1 = max(maxd1, d)
        if node.left:
            d += 1
            get_max_depth1(node.left)
            d -= 1
        if node.right:
            d += 1
            get_max_depth1(node.right)
            d -= 1

    get_max_depth1(root)

    maxd2 = 0
    def get_max_depth2(node, d):
        nonlocal maxd2
        maxd2 = max(maxd2, d)

        if node.left:
            get_max_depth2(node.left, d+1)
        if node.right:
            get_max_depth2(node.right, d+1)

    get_max_depth2(root, 0)

如果您想確切了解分配差異是什么,那么使用https://pypi.org/project/memory-profiler/運行應用程序應該會給您想要的答案。 但這僅適用於非常理論的方面,結果將特定於一個 CPython 版本,可能無法保持整體。

在實踐中,答案是:出於以下幾個原因,它們大致相同:

  • 其他代碼將主導性能(只是創建新的堆棧幀並且會占用更多空間)
  • 無論如何,你不能 go 這么深(默認遞歸限制為 1000 幀)
  • 你的數據不會那么大(二叉樹通常保持平衡,在 100000 級它會給你超過10^30102元素)
  • 一旦你開始關心像這樣的單個字節和限制,CPython 就不再是正確的答案了。 Cython 可能是最簡單的下一步,您可以在那里檢查生成的 C 程序的確切堆/堆棧使用情況。

Python 的數據 model用戶定義的函數定義為具有__closure__屬性,這具體化了function 閉包:封閉范圍的可讀/可寫值。 __closure__None單元格tuple
目前的標准(3.10)只定義了一個單元格有一個cell_contents屬性來表示單元格的值。 CellType不保證它提供了什么。

值得注意的是,單元格是否可寫並不取決於 function 是否將閉包捕獲為可讀(裸使用)或可讀/可寫( nonlocal聲明)。 兩者都是同一種細胞。


在實踐中,CPython¹ 將__closure__表示為一個常規tuple ,每個單元格表示為一個 40 字節的 object ,其中包含對其值的引用。

>>> def outer(a = 3):
...     def inner():
...         print(a)  # `a` is captured from outer scope
...     return inner
>>> outer()
<function __main__.outer.<locals>.inner()>
>>> outer().__closure__
(<cell at 0x10eac2ca0: int object at 0x109f26d30>,)
>>> outer().__closure__[0].cell_contents
3
>>> # size of the closure tuple and cell in bytes
>>> sys.getsizeof(outer().__closure__), sys.getsizeof(outer().__closure__[0])
(48, 40)

__closure__本身屬於 function object,而一個cell在所有關閉同一變量的函數之間共享。

相反,局部變量存儲為引用數組——每個 8 字節。 本地存儲屬於 function調用,因此多次調用 function 也會創建多個此類引用。

作為參考,只是上面的inner function object 的 shell 是 136 字節。 它的名稱和完全限定名稱分別為 54 和 69 字節。 它的字節碼是 45 個字節。 您可能甚至不知道存在的東西有許多額外的費用。
在嘗試保護 8 字節的單個塊時請記住這一點。


¹CPython 3.8.12 [Clang 11.0.0 (clang-1100.0.33.17)],64 位構建。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM