我认识的python(5)

848 阅读6分钟

解释器篇

python的虚拟机其实本质上就是模拟cpu执行过程,对应函数栈帧实现Python字节码的执行。
当虚拟机真正执行的时候,其面对的不是PyCodeObject而是P yPyFrameObject

typedef struct _frame{
    PyObject_VAR_HEAD
    struct _frame *f_back;
    PyCodeObject *f_code;
    PyObject *f_builtins;
    PyObject *f_globals;
    PyObject *f_locals;
    PyObject **f_valuestack;
    PyObject **f_stacktop;
    ....
    int f_lasti;
    int f_lineno;
    ....
    //动态内存,维护(局部变量+cell对象集合+free对象集合+运行时栈)所需要的空间
    PyObject *f_localsplus[1];
}

运行时候的状态

创建PyFrameObject过程

PyFrameObject *PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, PyObject *locals){
    PyFrameObject *f;
    Py_ssize_t extras, ncells, nfree, i;
    ncells = PyTuple_GET_SIZE(code->co_cellvals);
    nfrees = PyTuple_GET_SIZE(code->co_freevars);
    
    extras = code->code_stack + code->co_nlocals + ncells + nfrees;
    f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, extras);
    
    extras = code->con_nlocals + ncells + nfrees;
    f->f_valuestack = f->f_localsplus + extras;
    f->f_stacktop = f->f_valuestack;
    return f;
}

作用域

LEGB
一般分为两种引用方式,属性引用和名字引用,属性引用本质是到名字引用空间中查找一个名字所引用的对象。

解释器执行字节码核心代码

PyObject *PyEval_EvalFrameEx(PyFrameObject *f, int throwflag){
    ...
    why = WHY_NOT;
    ...
    for(;;){
        ...
        fast_next_opcode:
            f->f_lasti = INSTR_OFFSET();  //类似PC寄存器的作用
            opcode = NEXTOP();
            oparg = 0;
            if(HAS_ARG(opcode))
                oparg = NEXTARG();
        dispatch_opcode:
            switch(opcode){
                case NOP:
                    goto fast_next_opcode;
                case LOAD_FAST:
                    ...
            }
    }
    
}

why表明一个字节码指令执行结果。
enum Why_Code {
    WHY_NOT = 0x0001
    WHY_EXCEPTION = 0x0002 
    WHY_RERAISE = 0x0004 //在finally中触发异常
    WHY_RETURN = 0x0008
    WHY_BREAK = 0x0010
    WHY_CONTINUE = 0x0020
    WHY_YIELD = 0x0040
    
}

线程进程概念

程序在运行模式分为进程或者线程。这里就会引入一个概念,PyFrameObject本质上是属于某进程或者线程(当然最终肯定是与线程关联)。进程是一个资源的管理单元,线程是一个程序最小运行单元。多个线程共享进程地址空间的全局信息。

Cpu切换任务的时候必须保存线程的运行环境,类似操作系统进程调度。在python中一个线程拥有一个PyThreadState对象,而PyInterpreterState对象代表进程状态对象。

为了节省内存,cpython在实现中让多个线程共享相同的Module对象。一般来说一个python程序拥有一个interpreter对象,一个interpreter对象维护多个PyThreadState,多个Pythreadstate轮流使用字节码解释器。

typedef struct _is {
    struct _is *next;
    struct _ts *tstate_head;
    
    PyObject *modules;
    PyObject *sysdict;
    PyObject *builtins;
    ...
} PyInterpreterState

typedef struct _ts {
    struct _ts *next;
    PyInterpreterState *interp;
    struct _frame *frame;
    int recursion_depth;
    ...
    PyObject *dict;
    ...
    long thread_id;
} PyThreadState;

当python虚拟机开始执行的时候,会从frame中取出PyFrameObject设置为当前执行环境,创建新的栈帧则重新设置关系。

字节码与PyFrameObject

比如我们经常用到赋值语句: i = 1,其对应的字节码为:
0 LOAD_CONST 0 (1) 3 STORE_NAME 0 (i)

+对应的字节码

BINARY_ADD
    w = POP();
    v = TOP();
    if(PyInt_CheckExact(v) && PyInt_CheckExact(w)){
        register long a, b, i;
        a = PyInt_AS_LONG(v);
        b = PyInt_AS_LONG(w);
        i = a+b;
        if((i^a) <0 && (i^b)<0)
            goto slow_add;
        x = PyInt_FromLong(i);
    }
    else if (PyString_CheckExact(v) && PyString_CheckExact(w)){
        x = string_concatenate(v, w, f, next_instr);
        goto skip_decref_vx;
    }
    else{
        slow_add:
            x = PyNumber_Add(v, w);
    }
    Py_DECREF(v);
skip_decref_vx:
    Py_DECREF(w);
    SET_TOP(x);
    break;

逻辑语句的实现

if语句的逻辑比较简单,类似汇编指令里面的jump系列的命令,根据绝对地址或者相对地址找到下一条字节码指令。
for语句会涉及一个特别的字节码SETUP_LOOP

typedef struct _frame{
    ....
    int f_iblock;
    PyTryBlock f_blockstack[CO_MAXBLOCKS];
}

void PyFrame_BlockSetup(PyFrameObject *f, int type, int handler, int level){
    PyTryBlock *b;
    b = &f->f_blockstack[f->f_iblock++];
    b->b_type = type;
    b->b_level = level;
    b->b_handler = handler
}

函数对象

函数对象是在执行字节码的过程生成出来,其本质是包含了作用域和code对象的一个结构。

typedef struct {
    PyObject_HEAD
    PyObject *func_code;
    PyObject *func_globals;
    PyObject *func_defaults;
    PyObject *func_closure;
    PyObject *func_doc;
    PyObject *func_name;
    PyObject *func_dict;
    PyObject *func_weakreflist;
    PyObject *func_module;
} PyFunctionObj

PyFunctionObject是python代码在运行时动态产生的,静态信息保存在func_code之中。 对于一段代码而言,PyCodeObject对象只有一个,但PyFunctionObject可以有多个。

加载const中的code对象,然后创建一个function对象

MAKE_FUNCTION逻辑
PyObject* PyFunction_New(PyObject *code, PyObject *globals){
    PyFunctionObject *op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);
    static PyObject *__name__ = 0;
    if(op!=NULL){
        ...
        op->func_code = code;
        op->func_globals = globals;
        op->func_name = ((PyCodeObject *)code)->co_name;
        consts = ((PyCodeObject *)code)->co_consts;
        if(PyTuple_size(consts)>=1) {
            doc = PyTuple_GetItem(consts, 0);
            if(!PyString_Check(doc) && !PyUnicode_Check(doc))
                doc = Py_None
        }
        else 
            doc = Py_None
        ...
        }
        ...
    }
} 

不论CFunction和Method调用都会进入这个call_function

static PyObject* call_function(PyObject ***pp_stack, int oparg){
    int na = oparg & 0xff;
    int nk = (oparg>>8) & 0xff;
    int n = na + 2 * nk; //需要读取的参数个数
    
    PyObject **pfunc = (*pp_stack) -n -1;
    PyObject *func = *pfunc;
    PyObject *x, *w;
    
    if(PyCFunction_Check(func) && nk == 0){
        ...
    } else {
        if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL){
            ...
        }
        if(PyFunction_Check(func))
            x = fast_function(func, pp_stack, n, na, nk);
        else
            x = do_call(func, pp_stack, na, nk);
        ...
    }
}
...
return x;

fast_function里面的过程大致就是创建栈帧,然后从ThreadState中取出栈帧添加并调用执行新的栈帧
而且新栈帧的globals来源于之前栈帧的f_globals以及f_locals

函数的局部变量是存放在PyFrameObject的 *f_localsplus对应的空间里面,通过load_fast和store_fast进行操作

创建一个类A对应的字节码

class A(object):
    def __init__(self):
        c = 10
        print c

    def b(self):
	print "22"

类以及类型

在python中每个对象都有一个type对象,可以通过对象的__class__获取,对于instance的type对应是class对象,而对于class对象的type对应的就是metaclass对象。这个对象在python中对应的就是PyType_Type。

python中每个class对象直接或者间接会与object对象存在is_kind_of关系,type object对应的是PyObject_Type

对于int类型,其找到+对应的方法如下所示:
从PyInt_Type中找到符号表对应的__add__,对应找到函数簇的nb_add。

对于PyInt_Type中tp_dict的填充是在python启动后初始化过程中会动态地给内置类型对应的PyTypeObject中填充东西,其中就包括了tp_dict。

<type 'type'>对应是的PyType_Type

class ==> PyTypeObject
创建类的过程,为class设置基类关系,然后为其设置类型type,然后初始化dict内容

int PyType_Ready(PyTypeObject *type)
{
    PyObject *dict, *bases;
    PyTypeObject *base;
    Py_ssize_t i, n;
    
    //假设父类不存在以及非PyBaseObject_Type。所以type的父类为object
    base = type->tp_base;
    if(base == NULL && type != &PyBaseObject_Type){
        base = type->tp_base = &PyBaseObject_Type;
    }
    
    //该条件判断对象是否初始化完毕
    if(base && base->tp_dict == NULL){
        PyType_Ready(base)
    }
    
    //拷贝父类的类型对象赋值
    if (type->ob_type == NULL && base != NULL){
        type->ob_type = base->ob_type;
        ......
        
    }
    
}

这一步是进行tp_dict的填充

为pylist_Type填充__getitem__

创建类的字节码分析

比如某个类的定义如下

class A(object):

    def __init__(self):
        pass
    
    def f(self):
        pass
        
  1           0 LOAD_CONST               0 ('A')
              3 LOAD_NAME                0 (object)
              6 BUILD_TUPLE              1
              9 LOAD_CONST               1 (<code object A at 0x1073f73b0, file "test.py", line 1>)
             12 MAKE_FUNCTION            0
             15 CALL_FUNCTION            0
             18 BUILD_CLASS         
             19 STORE_NAME               1 (A)
             22 LOAD_CONST               2 (None)
             25 RETURN_VALUE 

大致的逻辑是加载A,object创建关系继承列表,加载code对象,创建一个function,然后调用function。function对应的内容其实就是函数code的内容。

python引擎在执行call_function其实对应是执行两次def函数,创建两个FunctionObject对象。

之后一个类的创建条件则满足了,调用build_class创建一个class对象 build_class逻辑

PyObject * build_class(PyObject *methods, PyObject *bases, PyObject *name){
    PyObject *metaclass = NULL, *result, *base;
    if(PyDict_check(methods))
        metaclass = PyDict_GetItemString(methods, "__metaclass__");
    if (metaclass != NULL)
        Py_INCRE(metaclass);
    else if (PyTuple_checks(bases) && PyTuple_Get_Size(bases)>0){
        base = PyTuple_Get_ITEM(bases, 0);
        metaclass = PyObject_GetAttrString(bases, "__class__");
    }else{
        ....
    }
    result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods, NULL);
    .....
    return result;
    
}

查看元类是否定义,没有则从父类第一个提取出__class__作为metaclass。

类和instance的__new__的差别

python的instance和class都拥有__dict__,因此不同对象可以设置不同的内容,不同的calss可以设置不同的内容。