首页 > 解决方案 > 在这个例子中,名称 *open* 是属于内置作用域还是全局作用域?

问题描述

考虑这个代码片段:

global open
print(open)

这给出了以下结果:

<built-in function open>

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

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

标签: pythonscopegloballookupbuilt-in

解决方案


首先,直接回答:

该名称open属于顶级命名空间。这实质上意味着“在全局变量中查找,回退到内置函数;分配给全局变量”。

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

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


根据参考文档

global语句是适用于整个当前代码块的声明。这意味着列出的标识符将被解释为全局变量。


但是,尽管文档的其他部分似乎暗示了什么,“解释为全局变量”实际上并不意味着“在全局命名空间中搜索”,而是“在顶级命名空间中搜索”,如名称解析中所述

通过搜索全局命名空间(即包含代码块的模块的命名空间)和内置命名空间(模块的命名空间)来解析顶级命名空间中的名称builtins。首先搜索全局命名空间。如果在那里找不到名称,builtins则搜索名称空间。

而“作为全局变量”的意思是“在全局命名空间中查找名称的方式相同”,也就是“在顶级命名空间中”。

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


另外,请注意,正如文档execeval文档中所解释的,即使代码运行时也不是这样exec

如果 globals 字典不包含 key 的值,则在该 key 下插入__builtins__对内置模块字典的引用。builtins这样,您可以通过将自己的__builtins__字典插入到全局变量中,然后再将其传递给exec().

最终,模块和exec脚本是如何执行的。

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


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

  • 在编译时:
    • 编译器为函数构建一个符号表,将名称分为自由变量(非局部变量)、单元变量(嵌套函数用作非局部变量的局部变量)、局部变量(任何其他局部变量)和全局变量(这在技术上当然意味着“顶级命名空间”变量)。这就是该global语句发挥作用的地方:它强制将名称添加到全局符号表中,而不是添加到不同的符号表中。
    • 然后它编译代码,并LOAD_GLOBAL为全局变量发出指令。(并且它将各种名称存储在代码对象的元组成员中,例如co_names全局变量和单元变量co_cellvars等。)
  • 在运行时:
    • 当从编译代码创建函数对象时,它会__globals__作为属性附加到它上面。
    • 当一个函数被调用时,它__globals__成为f_globals框架的。
    • 然后,解释器的 eval 循环LOAD_GLOBAL通过执行您对 that 的期望来处理每条指令f_globals,包括文档__builtins__中描述的回退到。exec

推荐阅读