繁体   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