它不需要定义变量为类型,不需要花括号 {} 和分号 ,改用严格的缩进来划分代码块。它负责把复杂的底层逻辑隐藏起来,让你把全部的精力用来解决真正的问题。代码可以编写得漂亮而优雅。编程是要解决问题的,设计良好、高效而漂亮的解决方案。Python的简单被底层虚拟机隐藏起来,看一下编译后的代码:Python 虚拟机在核心设计上同样是基于堆栈(Stack-based)的指令集体系结构。日常我们运行 Python 代码时,它并不会直接翻译成机器码,而是先由 Python 编译器编译成字节码(Bytecode),然后再交由 Python 虚拟机(PVM)逐条解释执行。
以下是 Python 虚拟机架构的核心组成部分和工作原理:
1. Python 虚拟机的核心架构组件Python 虚拟机的执行环境主要围绕以下三个核心概念构建:
代码对象(PyCodeObject):这是 Python 编译器编译源文件(.py)后的产物(通常缓存为 .pyc 文件)。它包含了静态的字节码指令、常量表、变量名列表等。它是虚拟机的“图纸”。
执行帧(PyFrameObject):当一个 Python 函数被调用时,虚拟机就会在内存中创建一个“帧(Frame)”。执行帧是动态的,对应函数运行时的上下文,它包含三个极其关键的区域:
局部变量表(Local Variables): 存放函数的参数和局部变量。
全局变量表(Global Variables): 存放模块级的全局变量。
求值栈(Evaluation Stack): 核心计算区域。所有的字节码指令都在这里“推入”和“弹出”数据。
评测循环(The Evaluation Loop):这是 PVM 的心脏。在源码中它是一个巨大的 switch-case 循环(在 CPython 中被称为 _PyEval_EvalFrameDefault)。它负责读取字节码指令,根据指令类型转到对应的 C 语言代码去执行。
2. Python 虚拟机的运行机制由于也是基于堆栈的架构,Python 虚拟机的运算逻辑和 JVM 非常像。我们来看一段简单的 Python 代码:
Pythondef add(a, b): return a + b
当你调用 add(1, 2) 时,Python 编译器生成的字节码以及虚拟机的操作步骤如下:
| 字节码指令 | 虚拟机内部操作 (在当前 Frame 中) |
LOAD_FAST 0 (a) | 从局部变量表把第一个参数 a(值 1)压入求值栈顶。 |
LOAD_FAST 1 (b) | 从局部变量表把第二个参数 b(值 2)压入求值栈顶。 |
BINARY_OP 0 (+) | 弹出栈顶的 2 和 1,调用底层的加法函数,算出 3,再把 3压回栈顶。 |
RETURN_VALUE | 弹出栈顶的 3,并作为函数的返回值返回,销毁当前 Frame。 |
3. Python 虚拟机与 Java 虚拟机的三大核心区别虽然它们都是“基于堆栈的虚拟机”,但因为设计哲学不同,Python 虚拟机有两个非常独特的个性:
① 纯粹的“动态类型”与对象包装JVM: 拥有明确的底层基本数据类型(如 iadd 代表整型加法,fadd 代表浮点数加法)。
PVM: Python 一切皆对象。求值栈里压入和弹出的绝不是纯粹的数字 1 或 2,而是指向 PyObject 结构体的指针。它的加法指令 BINARY_OP 在执行时,必须在运行时去检查指针指向的对象是否支持加法,从而引发“动态寻址”和类型检查,这也是 Python 相对较慢的核心原因之一。
② GIL(全局解释器锁)的存在在官方的 CPython 虚拟机中,为了保证线程安全和简化内存管理,设计了 GIL(Global Interpreter锁)。这意味着在任意一个时刻,只有一个线程能在虚拟机中运行字节码。所以 Python 虚拟机默认无法利用多核 CPU 进行多线程并行计算。
③ 垃圾回收机制(GC)Python 虚拟机采用引用计数(Reference Counting)为主,标记-清除(Mark and Sweep)和分代回收(Generational GC)为辅的垃圾回收机制。一旦求值栈或变量表不再引用某个指针,对象的引用计数归零,虚拟机会立即将其释放,这与 JVM 纯粹的分代垃圾回收大不相同。