python执行采用“编译+解释”混合机制:先将源码经词法分析、语法分析、语义分析生成字节码,再由pvm逐条执行;字节码存于.pyc文件中含魔数、时间戳及marshal序列化code对象,pvm基于栈和帧结构执行并受gil约束。

Python解释器执行代码时,并不是直接运行源码,而是先将.py文件编译为字节码(bytecode),再由Python虚拟机(PVM)逐条解释执行。这个“编译+解释”的混合机制,是理解Python性能、调试和底层行为的关键。
源码如何变成字节码
当你运行 python script.py 时,解释器会做以下事情:
- 词法分析:把源码按规则切分成token(如 def、+、变量名等)
- 语法分析:用语法规则(如Grammar)构建抽象语法树(AST)
- 语义分析与优化:检查作用域、常量折叠(如 2 + 3 → 5)、删除无用代码等
- 生成字节码:遍历AST,为每个操作生成对应的字节码指令(如 LOAD_CONST、BINARY_ADD)
这个过程发生在内存中,除非启用了缓存(.pyc文件),否则不落盘。你可以用 compile() 函数手动触发编译:
>>> code = compile("x = 1 + 2", "", "exec")>>> code.co_code # 字节码原始bytes
字节码长什么样
字节码是一系列操作码(opcode)和参数组成的序列,每条指令通常占2字节(1字节操作码 + 1字节参数,参数可扩展)。例如:
立即学习“Python免费学习笔记(深入)”;
>>> def f(): return 1 + 2>>> import dis
>>> dis.dis(f)
1 0 LOAD_CONST 1 (3)
2 RETURN_VALUE
这里 LOAD_CONST 1 表示从常量表(co_consts)中取出索引为1的值(即3),RETURN_VALUE 表示返回栈顶值。注意:1 + 2 已被编译期优化为常量3,不会生成 BINARY_ADD。
.pyc 文件是怎么来的
为了加速后续导入,Python会在首次导入模块时,把字节码写入 __pycache__/xxx.cpython-3X.pyc。它不是纯字节码,而是一个带头部的封装文件:
- 前4字节:Python版本魔数(magic number),用于校验兼容性
- 接下来8字节:时间戳(源码修改时间)和文件大小,用于判断是否过期
- 剩余部分:marshal序列化的code对象(含字节码、常量、名字、变量信息等)
运行 python -B script.py 可禁用.pyc生成;python -O 还会丢弃assert和__debug__相关字节码。
PVM 如何执行字节码
Python虚拟机本质是一个大循环(eval_frame_loop),对当前帧(frame)的字节码逐条取指、解码、执行。关键点包括:
- 每个函数调用都创建新帧对象,维护自己的局部变量、栈、指令指针(f_lasti)
- 操作基于栈:比如 LOAD_FAST x 把局部变量x压栈,STORE_FAST y 弹出栈顶存入y
- 遇到跳转指令(如 JUMP_IF_FALSE_OR_POP)会修改指令指针,实现分支/循环
- GIL(全局解释器锁)在此层生效,确保同一时刻只有一个线程执行字节码
虽然叫“解释器”,但现代CPython也做了不少优化:例如自适应的快速路径、内联缓存(针对属性访问)、以及即将在3.13中落地的“字节码专用JIT预编译”实验性支持。










