简体   繁体   English

python“nonlocal”关键字如何实现? 可以节省空间吗?

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

Now I am thinking of writing a code to get maximum depth of a binarytree.现在我正在考虑编写一个代码来获得二叉树的最大深度。 Python can't pass non-container parameter by reference, so there are generally two choices, use nonlocal keyword or pass depth by copy. Python 不能通过引用传递非容器参数,所以一般有两种选择,使用nonlocal关键字或通过复制传递深度。

The first one get_max_depth1 need more traceback operation, I wonder whether it costs less space compare to get_max_depth1 .第一个get_max_depth1需要更多的回溯操作,我想知道它是否比get_max_depth1花费更少的空间。 If python implement nonlocal use parameter pass by reference, then every function also bring an integer pointer, in this case, it is inferior to get_max_depth2 , because it's harder to write, slower to run, and save almost no space.如果 python 实现了nonlocal使用参数通过引用传递,那么每个 function 还带一个 integer 指针,在这种情况下,它不亚于get_max_depth2运行速度更慢,并且节省空间。 If not, when the binary tree depth is 100000, get_max_depth1 only need one variable, get_max_depth2 need 100000 variable d saved in their function, I guess it's meaningful to write d outside.如果没有的话,二叉树深度为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)

If you want to find out exactly what the allocation difference is, then running the app with https://pypi.org/project/memory-profiler/ should give you the answers you're after.如果您想确切了解分配差异是什么,那么使用https://pypi.org/project/memory-profiler/运行应用程序应该会给您想要的答案。 But that only applies to the very theoretical side and the result will be specific to one CPython version and may not hold overall.但这仅适用于非常理论的方面,结果将特定于一个 CPython 版本,可能无法保持整体。

In practice the answer is: they're about the same for a few reasons:在实践中,答案是:出于以下几个原因,它们大致相同:

  • Other code will dominate the performance (just creating new stack frames and would take more space)其他代码将主导性能(只是创建新的堆栈帧并且会占用更多空间)
  • You can't go that deep anyway (default recursion limit is 1000 frames)无论如何,你不能 go 这么深(默认递归限制为 1000 帧)
  • Your data will not be that large (binary trees are usually kept balanced which at 100000 levels it would give you over 10^30102 elements)你的数据不会那么大(二叉树通常保持平衡,在 100000 级它会给你超过10^30102元素)
  • Once you start caring about single bytes and limits like that, CPython stops being the right answer.一旦你开始关心像这样的单个字节和限制,CPython 就不再是正确的答案了。 Cython may be the simplest next step and there you can check the exact heap/stack usage of the resulting C program. Cython 可能是最简单的下一步,您可以在那里检查生成的 C 程序的确切堆/堆栈使用情况。

Python's data model defines User-defined functions as having a __closure__ attribute, which reify the function closure : the readable/writeable values of enclosing scopes. Python 的数据 model用户定义的函数定义为具有__closure__属性,这具体化了function 闭包:封闭范围的可读/可写值。 __closure__ is None or a tuple of cells . __closure__None单元格tuple
The standard currently (3.10) only defines that a cell has a cell_contents attribute to represent the value of the cell.目前的标准(3.10)只定义了一个单元格有一个cell_contents属性来表示单元格的值。 The CellType makes no guarantees what it provides. CellType不保证它提供了什么。

Notably, whether a cell is writeable is not determined by whether a function captures the closure as readable (bare usage) or readable/writeable ( nonlocal declaration).值得注意的是,单元格是否可写并不取决于 function 是否将闭包捕获为可读(裸使用)或可读/可写( nonlocal声明)。 Both are the same kind of cell.两者都是同一种细胞。


In practice, CPython¹ represents __closure__ as a regular tuple and each cell as a 40 byte object that holds a reference to its value.在实践中,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)

The __closure__ itself belongs to the function object, whereas a cell is shared between all functions closing over the same variable. __closure__本身属于 function object,而一个cell在所有关闭同一变量的函数之间共享。

In contrast, a local variable is stored as an array of references – each 8 byte.相反,局部变量存储为引用数组——每个 8 字节。 The local storage belongs to the function call , so calling a function multiple times also creates multiple such references.本地存储属于 function调用,因此多次调用 function 也会创建多个此类引用。

For reference, just the shell of the above inner function object is 136 bytes.作为参考,只是上面的inner function object 的 shell 是 136 字节。 Its name and fully-qualified name are 54 and 69 bytes, respectively.它的名称和完全限定名称分别为 54 和 69 字节。 Its bytecode is 45 bytes.它的字节码是 45 个字节。 There are many additional costs for things that you likely do not even know exist.您可能甚至不知道存在的东西有许多额外的费用。
Keep that in mind when trying to safe individual chunks of 8 bytes.在尝试保护 8 字节的单个块时请记住这一点。


¹CPython 3.8.12 [Clang 11.0.0 (clang-1100.0.33.17)], 64 bit build. ¹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