Python虚拟机整体框架

Python虚拟机的运行原理和exe文件在x86架构的CPU中执行的原理类似。都是通过栈帧的方式来执行的(其实就是一个栈指针在一个栈中移来移去,然后不停地出栈入栈,不停创建栈帧),至于具体怎么运行的比较麻烦,我就不说的,自己看书去。简单来说栈帧和python中命名空间是对应的。一个栈帧对应一个命名空间,所以一个栈帧也对应一个PyCodeObject对象。在Python中栈帧的抽象叫做PyFrameObject。

[frameobject.h]
typedef struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;     // 执行环境链上的前一个frame
    PyCodeObject *f_code;      // PyCodeObject对象
    PyObject *f_builtins;      // builtin命名空间
    PyObject *f_globals;       // global命名空间
    PyObject *f_locals;        // local命名空间
    PyObject **f_valuestack;   // 运行时栈的栈底位置
    PyObject **f_stacktop;     // 运行时栈的栈顶位置
    ...
    int f_lasti;       // 上一条字节码指令在f_code中的偏移位置
    int f_lineno;      // 当前字节码对应的源代码行
    PyObject *f_localsplus[1]; // 动态内存。(局部变量 + cell对象集合 + free对象集合 + 运行时栈)
} PyFrameObject;

书本上说这里的f_code存放的是一个待执行的PyCodeObject对象,而不是当前正在执行的PyCodeObject,我表示有点小怀疑,待会我去验证一下。f_builtins, f_globals, f_locals分别对应了三个PyDictObject,维护了builtin, global, 三个命名空间中name,以及local命名空间中name和value之间的对应关系。因为每个PyCodeBlock所需要的栈空间大小都是不一样的,所以PyFrameObject是一个可变长的对象,而f_localsplus所指向的就是PyCodeBlock所需要的栈空间和其他一些内存。实际上,运行时栈空间是f_valuestack和f_stacktop之间的空间,其他的空间是用来存放闭包变量的。Python标准库中的sys模块提供了对PyFrameObject的访问,具体的函数是sys._getframe()。

在Python中赋值语句会在local命名空间中创建约束(所谓的约束,就是变量名和变量名对应的值的对应关系)。赋值语句在Python中不单单指存在赋值符号的语句,还包括了def, class, import,for这样的语句。关于命名空间这个东西记住一点:Python中命名空间是静态的,而不是动态的。也就是说作用域仅仅由源程序的文本决定,而不是由运行时动态决定的(所以也叫词法作用域)。至于Python的LGB规则和LEGB规则我也不想多说了,只要按照最内嵌套作用域规则来推导就行了。关于Python的静态作用域,还有一个比较有意思的问题,之前我们老板面试我的时候问过我O(^_^)O

a = 1
def g():
    print a

def f():
    print a
    a = 2
    print a

g()
f()

当时我认为输出的结果是1 1 2。其实这段代码在运行f()第一个print a的时候就报错了,”local variable ‘a’ referenced before assignment”。简单的说,原因就是在编译成pyc文件的时候,f()中a的变量就已经确定是local变量了,所以在执行第一句print a的时候,发现a是未定义的,所以就报错了。通过dis查看编译生成的字节码就能看出两个print a的差异(g()中的print a和f()中的第一个print a的差异)。

g()中print a的字节码为
# 0 LOAD_GLOBAL        0
# 3 PRINT_ITEM
# 4 PRINT_NEWLINE

f()中第一句print a的字节码为
# 0 LOAD_FAST          0
# 3 PRINT_ITEM
# 4 PRINT_NEWLINE

LOAD_GLOBAL的意思是从global空间中加载变量 而 LOAD_FAST是从local空间中加载变量。

在Python中Python解释器充当了CPU的角色,Python解释器中可能运行了很多进程,每个进程中可能有很多线程,而线程下面又有很多栈帧。这样一想整个Python虚拟机的框架就出来了。O(^_^)O