python为什么使用中间单元格进行闭包



伙计们,我很不明白为什么python使用中间单元格作为闭包。例如:

def outer():
x = "world"
def inner():
print(f"Hello {x}")
return inner

现在x.outer和x.inner(忽略点符号,这只是为了区分两个变量)都指向同一个中间单元格,而这个中间单元格又指向包含字符串对象的内存单元格。

我们从这个中间细胞得到了什么?为什么这两个变量不能直接指向包含字符串对象的内存单元?

从引用计数的角度来看,即使在outer()函数完成运行后,我们仍然有1的引用计数(因为我们仍然有x.inner变量),所以python内存管理器将无法清空这个中间单元格。但是,如果这两个变量直接指向包含字符串对象的内存单元,那么我们将具有相同的引用计数。所以我想这与参考计数无关。

那个么,这个中间细胞背后的想法是什么?为什么我们需要使用它?谢谢

这个想法对我来说似乎很清楚——如果我能写出来的话:在Python中,变量本身并不处于固定的内存位置,而是引用放置在该内存位置的任何内容,就像静态语言一样。他们也不直接";包含对存储器中的对象的引用(指针)";。发生的情况是,它们间接地包含这个引用,而这种间接的发生方式取决于变量的种类。(读到底)

首先,让我们理解,如果没有"中间体";单元格对象,内部作用域中x变量的赋值只会改变x所指向的对象,而外部作用域中的x永远不会知道这种值的更改:它只会继续引用上一个对象。

非局部变量的情况是,编译器生成的字节码将读取值并将值写入单元格对象,从而使共享同一变量的所有变量中的值始终同步。但对于使用这些值的代码,在Python源代码中,它是完全透明的:字节码将始终被构建为加载并将值设置为单元格-而对于内部范围变量中未使用的普通变量,将发出不同的字节码,这将把给定变量的值保存在不同的区域(快速局部变量或全局变量)。

因此,换言之:Python中有3种根本不同的变量类型,编译器在编译时知道(*)给定的变量是哪种类型,并且它将为每种类型发出不同的字节码。非局部变量获取字节码,该字节码使用cell对象(就好像它是静态语言中的一个内存位置)为自己存储值。((*)-如果在编译时变量的类型存在歧义,编译器只会出错。尽管有一些遗留的字节码指令会在所有有效的范围内按名称搜索变量,但我认为Python 3.11中的代码不再生成这些指令了。)

说明上面第二段中的示例-检查此代码:


def a():
x = 1
def c():
nonlocal x
x = 2
c()
print(x)

如果CCD_ 5s将简单地";指向一个值";,当在内部函数c中将其更改为2时。

查看a:的反汇编代码的最后部分

7          38 LOAD_GLOBAL              1 (NULL + print)
50 LOAD_DEREF               1 (x)
52 PRECALL                  1
56 CALL                     1

当内部函数不使用a:中的变量时,将其与相同代码进行比较

In [4]: 
...: def a():
...:    x = 1
...:    def c():
...:       y = 2
...:    c()
...:    print(x)
...
7          32 LOAD_GLOBAL              1 (NULL + print)
44 LOAD_FAST                0 (y)
46 PRECALL                  1
50 CALL 

如上所述,Python使用不同的操作码策略:在两种情况下,LOAD_DEREFLOAD_FAST:;DEREF";操作码是将值存储到Cell对象并从中检索值的操作码。事实上,我们可以从Python代码中检查并访问Cell对象,这几乎是一个"错误";奢侈"-如果语言是完全不透明的,它也会起同样的作用。

至于你对参考计数的关注,我们进入了最后一部分:;x〃;正如您所看到的,name只存在于源代码中。在上面的第一种情况中,每当我们使用x变量时,只有一个对将检索的值的引用:单元格引用的对象。这会让人头晕目眩,实际上:Python变量不会"存在";当程序正在执行时;存在";只有在存储值或从中检索值的点上,编译器才会发出适当的字节码,以从适当的容器中存储或检索该值。对于全局变量,该容器是模块globals()字典(回退到builtins模块)。对于局部变量,它通常是当前帧中的一个槽,由LOAD_FASTSTORE_FAST使用(这些槽的容器在需要时镜像到locals()字典),对于非局部变量或与内部作用域共享的变量,容器是Cell对象。

所有这一切的最终结果是;x〃;在上面这样的闭包中使用时,它的行为就像是同一个变量,并且计数只是对其值的一个引用,即使有几个内部函数使用同一个单元格也是如此。同时,如果在任何时候我们创建第二个变量,并执行y = x, whatever is the type ofy(local to the outer or any of the inner functions, nonlocal or global) a second reference to the value ofxis created and placed in the appropriate container fory`。它的工作原理就像魔术一样,但它只是一些逻辑设计。

最新更新