名称 *open* 属于本例中的内置作用域还是全局作用域?

Does the name *open* belong to the built-in or the global scope in this example?

考虑这个代码片段:

global open
print(open)

结果如下:

<built-in function open>

我的问题是:名称open在这个例子中属于内置作用域还是全局作用域?

我认为全局声明会强制将名称 open 映射到全局范围(因此会导致我们出错),但这并没有发生这里。为什么?

第一,直接回答:

名称open属于顶级命名空间。这实质上意味着 "look up in globals, fallback to builtins; assign to globals"。

添加 global open 只是强制它属于顶级名称空间,它已经存在。 (我假设这是顶级代码,不在函数内部或 class。)

这似乎与您阅读的内容相符吗?嗯,有点复杂。


根据the reference docs

The global statement is a declaration which holds for the entire current code block. It means that the listed identifiers are to be interpreted as globals.


但是,尽管文档的其他部分似乎暗示了什么,"interpreted as globals" 实际上并不意味着 "searched in the global namespace",而是 "searched in the top-level namespace",如 Resolution of names 中所述:

Names are resolved in the top-level namespace by searching the global namespace, i.e. the namespace of the module containing the code block, and the builtins namespace, the namespace of the module builtins. The global namespace is searched first. If the name is not found there, the builtins namespace is searched.

而 "as globals" 表示 "the same way that names in the global namespace are looked up",又名 "in the top-level namespace"。

而且,当然,对顶级命名空间的分配始终是全局变量,而不是内置变量。 (这就是为什么你可以首先使用全局 open 隐藏内置 open。)


此外,请注意,如 exec and eval 文档中所述,即使 this 对于代码 运行 到 [=17= 也不完全正确]:

If the globals dictionary does not contain a value for the key __builtins__, a reference to the dictionary of the built-in module builtins is inserted under that key. That way you can control what builtins are available to the executed code by inserting your own __builtins__ dictionary into globals before passing it to exec().

exec 最终是模块和脚本的执行方式。

因此,真正发生的事情——至少在默认情况下——是搜索全局命名空间;如果找不到该名称,则在全局命名空间中搜索 __builtins__ 值;如果那是模块或映射,则会搜索它。


如果您特别好奇这在 CPython 中是如何工作的:

  • 编译时:
    • 编译器为函数构建一个符号 table,将名称分成 freevars(非局部变量)、cellvars(被嵌套函数用作非局部变量的局部变量)、局部变量(任何其他局部变量)和全局变量 (这当然在技术上意味着 "top-level namespace" 变量)。这就是 global 语句发挥作用的地方:它强制将名称添加到全局符号 table 而不是其他符号。
    • 然后它编译代码,并为全局变量发出 LOAD_GLOBAL 指令。 (并且它将各种名称存储在代码对象的元组成员中,例如 co_names 用于全局变量,co_cellvars 用于 cellvars 等等。)
  • 在运行时间:
    • 当从编译代码创建函数对象时,它会 __globals__ 作为属性附加到它。
    • 当一个函数被调用时,它的 __globals__ 变成框架的 f_globals
    • 解释器的 eval 循环然后通过完全按照您对 f_globals 的期望来处理每个 LOAD_GLOBAL 指令,包括回退到 __builtins__,如 [=17] 中所述=] 文档。