python - Python:为什么解释器在正在执行的行下方的函数中搜索变量赋值?
问题描述
语境
我对 python / 计算机编程相对较新,并试图通过玩弄一些基本概念来理解它们。
在我的理解中,python 是一种解释性语言,即它在执行代码时评估代码行。
问题
在一个函数中,为什么解释器通过向下代码检查变量赋值而不执行它所在的行,特别是因为所有相关信息都可用?
例子:
def spam():
eggs = 'spam local'
print(eggs)
global eggs
print(eggs)
eggs = 'global'
print(eggs)
错误: 上面的代码导致以下错误:
“语法错误:在全局声明之前使用名称‘eggs’”
混乱:
在我看来,第二行明确地将“垃圾邮件本地”分配给变量“eggs”。因此,当解释器到达第 3 行,即 print(eggs) 时,它应该打印“spam local”,而不是通过代码查看变量的全局声明并返回 SyntaxError。
鉴于所有相关信息都可用,为什么解释器不执行它的行?
解决方案
在我的理解中,python 是一种解释性语言,即它在执行代码时评估代码行。
No. 1 Python 一次编译一个模块。所有函数体都编译成字节码,顶层模块代码编译成字节码。这就是.pyc
文件的本质——所有这些字节码对象都编组到一个容器中。2
当你import
是一个模块时,它的顶级字节码会被解释。(如果没有.pyc
文件,.py
则动态编译该文件以获取该字节码。)
还有一件事一开始可能看起来并不明显:在编译函数体时,一条def
语句(或lambda
表达式)被编译成可解释的字节码,基本上是对从函数体的编译字节码构建函数对象的函数的调用。
如果您愿意,您可以手动重现所有这些,这对于试验事物的工作方式非常方便。您可以使用 mode 调用compile
某些模块源以exec
相同的方式编译它import
,并且您可以调用exec
结果以相同的方式运行它import
,您甚至可以使用模块从头开始marshal
构建自己的文件或.pyc
加载它们。(事实上,如果您使用的是 Python 3.3+,这不仅等同于做什么import
,它正是做什么import
;importlib
是用 Python 编写的。)
inspect
您还可以使用anddis
模块查看编译器已经完成的工作(或将在尚未编译的源代码上执行的操作,并且只是作为字符串) 。
编译函数体过程的一部分是检测哪些名称是本地的、单元的、自由的或全局的。在函数的整个生命周期中,名称始终只是一种,这使得事情更容易推理。
忘记 cellvar/freevar 的情况(只需要闭包),规则很简单:
- 如果
global
函数体中有名称的声明,则它是全局的。 - 否则,如果在函数体中有对名称的赋值,则3它是本地的。
- 否则,它是全球性的。
有关完整的详细信息(以及稍微更准确的详细信息4),请参阅参考文档中的命名和绑定。
global
在 2.2 之前的 Python 版本中,分配给一个变量然后稍后在同一个函数中声明它是合法的。在 2.1 中,这遵循了上面解释的规则:第一个分配是全局的。这是非常具有误导性的,特别是因为它几乎总是只是偶然发生的。这大概就是为什么这样做是错误的。在早期版本中,事情只是疯了。5
1. 好吧,在交互式解释器中,它确实是这样做的——但即使在那里,它也是一次一个语句,而不是一次一行。def
例如,像or这样的四行复合语句for
会同时编译和解释。
2. 实际上,“容器”位相当微不足道。事实上,一切都递归地已经是顶级模块字节码对象的一部分——例如,顶级函数的名称、字节码等都只是存储在模块字节码中的常量值,就像任何其他常量值一样。
3. 算作赋值的事物,包括作为参数,或者作为赋值语句、del
语句、for
语句或子句、as
子句、an import
、adef
或class
3.8+中的目标列表的目标或成员赋值表达式。
4. Python 实现实际上不必在编译时确定所有这些东西,只要语义最终相同。所以参考是用即使没有编译器的实现也可以遵循的术语编写的。但在实践中,至少 CPython、PyPy、MicroPython、Jython 和 IronPython 会在编译时确定名称绑定。
5. Python 0.9 通常没有global
, 和不同的作用域规则。在 1.1 中,据我所知,规则是语句之前的赋值global
重新绑定全局变量(如果已经存在)(您仍然在框架中设置一个局部变量,但它保持未绑定),否则绑定一个局部变量,这对我来说毫无意义。我从来没有设法在现代系统上构建 1.5、1.6 或 2.0,但代码明显不同于早期的 1.x 和 2.1,所以据我所知,它们实际上做了你在这里所期望的......或者也许他们做了一些和 1.1 一样疯狂的事情,但完全不同。