当前位置:首页>java>只用5行代码,说明Python也是有编译阶段

只用5行代码,说明Python也是有编译阶段

  • 2026-10-21 13:28:09
只用5行代码,说明Python也是有编译阶段
只用5行代码,说明Python也是有编译阶段

Python 虽然常说“解释执行”,但内部是有明确的“编译阶段”的,只是这个编译是即时完成(JIT-like 也不是严格 JIT,而是 CPython 的编译执行流程)。

示例代码与报错

img

运行时,在第3行print(x)就报错,而不用等到第4行。为何?

# python test.pyTraceback (most recent call last):  File "/root/study-python-vis/test.py", line 5, in <module>    foo()  File "/root/study-python-vis/test.py", line 3, in foo    print(x)          ^UnboundLocalError: cannot access local variable 'x' where it is not associated with a value

查看反编译字节码

img

左边对应源码行号

从上图dis反编译的字节码可看出,第3行的 LOAD_FAST_CHECK 会检查变量初始化,第4行已不检查了。说明这个函数内部,在编译期间对变量 x 进行了统筹处理。

  • • LOAD_FAST_CHECK(*var_num*):将对局部变量co_varnames[var_num]的引用压入堆栈,如果局部变量尚未初始化,则引发UnboundLocalError 。Added in version 3.12.
  • • LOAD_FAST(var_num):将对局部变量co_varnames[var_num]的引用压入栈中。*3.12 版本更改:*此操作码现在仅用于保证局部变量已初始化的情况。它不会引发UnboundLocalError。
  0           RESUME                   0  1           LOAD_CONST               0 (1)              STORE_NAME               0 (x)  2           LOAD_CONST               1 (<code object foo at 0x60e3e8094eb0, file "example.py", line 2>)              MAKE_FUNCTION              STORE_NAME               1 (foo)  5           LOAD_NAME                1 (foo)              PUSH_NULL              CALL                     0              POP_TOP              RETURN_CONST             2 (None)foo  2           RESUME                   0  3           LOAD_GLOBAL              1 (print + NULL)              LOAD_FAST_CHECK          0 (x)              CALL                     1              POP_TOP  4           LOAD_FAST                0 (x)              LOAD_CONST               1 (1)              BINARY_OP               13 (+=)              STORE_FAST               0 (x)              RETURN_CONST             0 (None)

结果看完了,现在来说说两个阶段:编译与执行。

Python 的执行流程:编译 + 执行

Python 的执行并非逐行直接解释源代码,而是有两个主要阶段:

源代码 (.py)  --> 编译阶段 --> 字节码 (.pyc) --> 解释器执行

a) 编译阶段(Compile)

  • • Python 读取源代码,解析成 抽象语法树(AST)。
  • • AST 被转成 code object(字节码)。
  • • 编译阶段会:
    • • 检查语法错误
    • • 确定 作用域(local/global/nonlocal)
    • • 标记哪些变量是局部变量
    • • 分配栈大小、生成指令
  • • 编译阶段 不执行代码(除了顶层表达式求值的特殊情况)。

所以,foo() 中 x += 1 就已经在编译阶段把 x 标记为局部变量。

b) 执行阶段(Run/Interpret)

  • • CPython 解释器(ceval.c)逐条执行字节码:
    • • 如:LOAD_FAST / LOAD_GLOBAL / BINARY_OP / CALL 等。
  • • 执行阶段才是真正“解释”代码,做计算、调用函数、修改内存等。
  • • 如果在执行阶段访问局部变量但它未初始化,就会报 UnboundLocalError。

看清编译字节码的结构

img

粗看,dis反编译字节码中第2行出现两回重复了。其实是 模块级字节码 + 函数字节码 的区别。

a) 模块级字节码

  0           0 RESUME                   0  1           2 LOAD_CONST               0 (1)              4 STORE_NAME               0 (x)  2           6 LOAD_CONST               1 (<code object foo ...>)              8 MAKE_FUNCTION            0             10 STORE_NAME               1 (foo)  5          12 PUSH_NULL             14 LOAD_NAME                1 (foo)             16 CALL                     0             24 POP_TOP             26 RETURN_CONST             2 (None)

解释:

  • • LOAD_CONST 1 (<code object foo ...>)
    • • Python 编译 foo 函数体时生成一个 code object,存在模块常量表里。
  • • MAKE_FUNCTION
    • • 根据 code object 创建函数对象。
  • • STORE_NAME foo
    • • 把函数绑定到模块变量 foo。

注意:这里 LOAD_CONST 1 的常量是函数的 code object,而不是源代码再次编译。

b) 函数字节码

Disassembly of <code object foo ...>:  2           0 RESUME                   0  3           2 LOAD_GLOBAL              1 (NULL + print)             12 LOAD_FAST_CHECK          0 (x)             14 CALL                     1             22 POP_TOP  4          24 LOAD_FAST                0 (x)             26 LOAD_CONST               1 (1)             28 BINARY_OP               13 (+=)             32 STORE_FAST               0 (x)             34 RETURN_CONST             0 (None)

解释:

  • • 这是 函数体内部的字节码。
  • • 编译器在解析 def foo(): ... 时生成了一个独立的 code object。
  • • 第 2 行在模块字节码里是 MAKE_FUNCTION,在函数字节码里是 RESUME 等指令。
  • • 两处看似“重复”,其实是:
  1. 1. 模块级:创建函数对象
  2. 2. 函数体内部:执行函数内容

好也,现在再说说作用域,为何函数里边是另一个作用域。

作用域规则(LEGB)

Python 的作用域遵循 LEGB 原则:

  • • Local(局部)
  • • Enclosing(闭包)
  • • Global(全局)
  • • Built-in

在函数里,如果你 给一个变量赋值,Python 会把这个变量 默认认为是局部变量(除非你用 global 或 nonlocal,比如global x)。

为什么 Python 看起来像解释执行?

  • • CPython 在用户眼里是 一行一行执行,所以大家叫它“解释执行”。
  • • 但是 每个函数或模块执行前都会先编译成字节码。
  • • 这个编译是即时的(just-in-time 编译到字节码,不是机器码),不生成独立机器码文件,所以你感觉像解释执行。

最新文章

随机文章