Python: 为什么解释器在正在执行的行下面的函数中搜索变量赋值?

Python: Why does interpreter search for variable assignment in a function below the line being executed?

上下文

我对 python / 计算机编程还比较陌生,我试图通过玩弄一些基本概念来了解它们。

在我看来,python 是一种解释型语言,即它在执行代码行时对其求值。

问题

在一个函数中,为什么解释器通过向下查看代码而不是执行它所在的行来检查变量赋值,尤其是因为所有相关信息都可用?

示例:

def spam():

    eggs = 'spam local'
    print(eggs)

    global eggs
    print(eggs)                      

eggs = 'global'

print(eggs) 

错误: 上面的代码导致以下错误:

"SyntaxError: name 'eggs' is used prior to global declaration"

困惑:

在我看来,第 2 行明确地将 "spam local" 赋值给变量 'eggs'。因此,当解释器到达第 3 行时,即 print(eggs),它应该打印 "spam local" 而不是向下看代码以查看变量的全局声明和 return SyntaxError.

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

In my understanding, python is an interpreted language i.e. it evaluates the line of code as it is being executed.

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

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

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


如果需要,您可以手动重现所有这些内容,这对于试验事物的工作方式非常方便。你可以调用compile on some module source with exec mode to compile it the same way an import would, and you can call exec on the result to run it the same way an import would, and you can even play with the marshal module to build your own .pyc files from scratch or load them up. (In fact, if you're on Python 3.3+, this isn't just equivalent to what import does, it's exactly what import does; importlib写成Python.)

您还可以使用 inspect and dis 模块查看编译器已完成的操作(或将对尚未编译且仅作为字符串的源代码执行的操作)。


编译函数体过程的一部分是检测哪些名称是本地的、单元格的、自由的或全局的。在函数的整个生命周期中,名称始终只有一种,这使事情更容易推理。

忘记 cellvar/freevar 个案例(仅在闭包时需要),规则非常简单:

  • 如果函数体中的名称有 global 语句,则它是全局的。
  • 否则,如果在函数体中有对名称的赋值,3它是局部的。
  • 否则就是全局的。

有关完整的详细信息(以及稍微更准确的详细信息4),请参阅参考文档中的Naming and binding

在 2.2 之前的 Python 版本中,赋值给一个变量然后在同一函数中声明它 global 是合法的。在 2.1 中,这遵循了上面解释的规则:第一个赋值是全局的。这是非常具有误导性的,尤其是因为它几乎总是只是偶然发生的。这大概就是为什么这样做是错误的。在早期版本中,事情简直是疯了。5


1.好吧,在交互式解释器中,它 确实 有点这样做——但即使在那里,它也是一次一个语句,而不是一次一行。例如,像 deffor 这样的四行复合语句会被一次性编译和解释。

2。实际上,"container" 位非常简单。事实上,一切都递归地已经是顶级模块字节码对象的一部分——例如,顶级函数的名称、字节码等都只是存储在模块字节码中的常量值,就像任何其他常量值一样。

3。算作赋值的事物包括作为参数,或作为赋值语句、del 语句、for 语句或子句、as 子句的目标或目标列表成员、importdefclass,或者在 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 一样疯狂的事情,但完全不同。