首页 > 解决方案 > 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。

鉴于所有相关信息都可用,为什么解释器不执行它的行?

标签: pythonvariablesgloballocalinterpreter

解决方案


在我的理解中,python 是一种解释性语言,即它在执行代码时评估代码行。

No. 1 Python 一次编译一个模块。所有函数体都编译成字节码,顶层模块代码编译成字节码。这就是.pyc文件的本质——所有这些字节码对象都编组到一个容器中。2

当你import是一个模块时,它的顶级字节码会被解释。(如果没有.pyc文件,.py则动态编译该文件以获取该字节码。)

还有一件事一开始可能看起来并不明显:在编译函数体时,一条def语句(或lambda表达式)被编译成可解释的字节码,基本上是对从函数体的编译字节码构建函数对象的函数的调用。


如果您愿意,您可以手动重现所有这些,这对于试验事物的工作方式非常方便。您可以使用 mode 调用compile某些模块源以exec相同的方式编译它import,并且您可以调用exec结果以相同的方式运行它import,您甚至可以使用模块从头开始marshal构建自己的文件或.pyc加载它们。(事实上​​,如果您使用的是 Python 3.3+,这不仅等同于做什么import,它正是做什么importimportlib是用 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、adefclass3.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 一样疯狂的事情,但完全不同。


推荐阅读