如果Python一次执行一行,为什么它们在声明之前就能看到变量?

时间:2013-10-04 00:57:17

标签: python python-3.x compilation scope

x = 4
def test():
    print(x)
    x = 2
test()

这会产生错误,因为当您转到print(x)时,它会看到您在函数x的范围内声明了test,并且它告诉您正在尝试在没有声明的情况下引用它。

我知道如果我做global x这没问题,或者我移动了打印声明......我知道。

但我不明白解释器如何知道我在print语句后重新声明x如果它一次通过代码一行。怎么知道会发生什么呢?

这显然比我所了解的更多。

3 个答案:

答案 0 :(得分:7)

谁告诉你Python一次执行一行? Python一次执行一个字节码。该字节码来自编译器,它一次运行一个语句。语句可以是多行。函数定义是一种陈述。

因此,编译函数定义的第一步是收集在该函数体内分配的所有变量。已分配但没有globalnonlocal声明的任何变量都是本地变量。

(作为旁注,该函数体实际上并没有被编译成函数,它被编译成一个code对象,它被隐藏在某个地方,只在你调用函数时运行,并进入一些字节码从function对象构建一个code对象,该对象在正常顺序中运行函数定义的位置运行。)

事实上,您可以通过查看其成员来查看编译器对您的函数的作用:

>>> def foo():
...     global y
...     x=1
...     y=1
>>> foo.__code__.co_varnames
('x',)

然后,当它为你的函数体创建字节码时,co_varnames中的所有变量都被编译成本地查找,而其余变量被编译成全局查找:

>>> dis.dis(foo)
  3           0 LOAD_CONST               1 (1)
              3 STORE_FAST               0 (x)

  4           6 LOAD_CONST               1 (1)
              9 STORE_GLOBAL             0 (y)
             12 LOAD_CONST               0 (None)
             15 RETURN_VALUE        

答案 1 :(得分:4)

Python确实一次执行一条指令;只是函数定义是单指令。当Python解释器遇到函数定义时,它会编译函数:首先将其转换为抽象语法树(AST),然后转换为字节码。在此过程中,Python“向前看”并查看哪些变量应被视为本地变量(通过扫描AST并查看分配给但未声明为全局的名称)。

当调用该函数时,它一次执行一条指令,但编译过程可以自由地考虑整个函数,因为它被认为是单个操作。这对于各种优化也很有用。

答案 2 :(得分:1)

如果您将此程序视为三个离散操作:

x = 4           # variable assignment 
def test(): foo # function definition
test()          # function call

它更有意义。解释器处理函数定义 - 这需要计算变量的范围等,从而导致错误。