python3.6 源码分析(五):类的创建

来源:互联网 发布:看门狗pc优化补丁 编辑:程序博客网 时间:2024/06/05 06:54

友情提示:类的创建过程非常复杂, 请自备小本本

字节码分析

先来个最简单的类:

class A:    pass

编译一下:

              0 LOAD_BUILD_CLASS              2 LOAD_CONST               0 (<code object A at 0x00000226D1158ED0, file "", line 1>)              4 LOAD_CONST               1 ('A')              6 MAKE_FUNCTION            0              8 LOAD_CONST               1 ('A')             10 CALL_FUNCTION            2             12 STORE_NAME               0 (A)

可以看到第一条字节码就不认识了,好吧,暂时不管他是干啥的,往下看,接下来三条字节码创建了一个函数对象,codeobject正是类的定义体,名字叫A,欸?定义类怎么变成定义函数了?
好吧,带着问题继续看,CALL_FUNCTION,终于调用了!等等,CALL_FUNCTION 的参数是2,说明有2个参数,往上数两个,正好是’A’和刚才创建的函数对象,参数有了,那被调用的函数呢,没了?这是不可能的,唯一的理由就是LOAD_BUILD_CLASS在堆栈中push了一个函数,看看LOAD_BUILD_CLASS的源码:

bc = _PyDict_GetItemId(f->f_builtins, &PyId___build_class__);

原来是builtins里面的一个叫_build_class_的东西,这个东西可能就是开始创建类的第一现场!

build_class

是时候来分析下过程了,全局搜索一下build_class,找到了这个一个函数:builtin___build_class__,看样子应该没错了,看下长短,150行。还行:
首先肯定是检查参数:

if (!PyTuple_Check(args)) {        PyErr_SetString(PyExc_TypeError,                        "__build_class__: args is not a tuple");        return NULL;    }    nargs = PyTuple_GET_SIZE(args);    if (nargs < 2) {        PyErr_SetString(PyExc_TypeError,                        "__build_class__: not enough arguments");        return NULL;    }    func = PyTuple_GET_ITEM(args, 0); /* Better be callable */    if (!PyFunction_Check(func)) {        PyErr_SetString(PyExc_TypeError,                        "__build_class__: func must be a function");        return NULL;    }    name = PyTuple_GET_ITEM(args, 1);    if (!PyUnicode_Check(name)) {        PyErr_SetString(PyExc_TypeError,                        "__build_class__: name is not a string");        return NULL;    }

可以看出来一点有用的信息:
1. args是tuple,这是cfunction函数调用约定,这里不多说了
2. args至少长度为2,是的,至少得有上面字节码里的类名和functionobject
3. 然后取出类名和functionobject放在func和name变量里备用

然后获取基类列表:

bases = PyTuple_GetSlice(args, 2, nargs);

这个很容易理解,前两个参数刚才已经用了,后面的位置参数肯定就是基类了。
确定元类是一个略微复杂的过程:
如果关键字参数为空,也就是metaclass没有指定:
1. 没有基类
钦定为type

meta = (PyObject *) (&PyType_Type);
  1. 有基类
    取第一个基类的metaclass
meta = (PyObject *) (base0->ob_type); 

这还没完呢,取得了暂时的metaclass还要去和基类列表比较,看有没有冲突之类的,这里就略过了,不是很重要。。

然后调用了meta的_prepare_:

prep = _PyObject_GetAttrId(meta, &PyId___prepare__);ns = _PyObject_FastCallDict(prep, pargs, 2, mkw);

这个ns默认情况下就是一个空的dict,然而却很重要,因为他就是我们创建的类的_dict_,所以我们可以通过复写元类的_prepare_方法改变这个规则,比如改成一个orderdict?
还有最后一个准备工作,那就是求值类体,用上面的ns作为locals,这样,所有在类中定义的名字都放入了ns

准备工作做完后,要进入正式的类的创建过程了,首先构造参数:

PyObject *margs[3] = {name, bases, ns};

分别是类名,基类列表,dict(看到了吧)。
然后调用元类:

cls = _PyObject_FastCallDict(meta, margs, 3, mkw);

很明显会进入meta的_call_,然而在call中,直接调用了_new_:

obj = type->tp_new(type, args, kwds);

所以还是直接看_new_把:
500多行代码,列出来没人看。。。。捡重要的说。
国际惯例,参数验证:
如果meta就是PyType_Type参数只有一个,那么就是type(x)这种东西了,很简单,直接返回x的类型:

 return (PyObject *) Py_TYPE(x);

然后是取参数,验证metadata,我们就不看了。直接看创建过程把

首先是基类,如果基类tuple为空,则给个PyBaseObject_Type,即object,所有类的基类。
然后处理dict,就是上面的ns,保存了所有类里面定义的名字
1. _slots_
第一步是看看类里面有没有定义slots,如果不知道slots是什么,请先看python教程。。。。。如果有,则进行一些名字改造,拼接,一般是拼上一个 _classname

__private =>  _classname__private

然后赋值给ht_slots域。这里有一点,动态创建的类和内建类型有点不一样,动态创建的类是PyHeapTypeObject,而内建的类是PyTypeObject。区别就在PyHeapTypeObject后面跟了额外几个域:

typedef struct _heaptypeobject {    /* Note: there's a dependency on the order of these members       in slotptr() in typeobject.c . */    PyTypeObject ht_type;    PyAsyncMethods as_async;    PyNumberMethods as_number;    PyMappingMethods as_mapping;    PySequenceMethods as_sequence; /* as_sequence comes after as_mapping,                                      so that the mapping wins when both                                      the mapping and the sequence define                                      a given operator (e.g. __getitem__).                                      see add_operators() in typeobject.c . */    PyBufferProcs as_buffer;    PyObject *ht_name, *ht_slots, *ht_qualname;    struct _dictkeysobject *ht_cached_keys;    /* here are optional user slots, followed by the members. */} PyHeapTypeObject;

就是普通的pytypeobject后面跟了一堆东西。
然后赶紧利用起来:

    type->tp_as_async = &et->as_async;    type->tp_as_number = &et->as_number;    type->tp_as_sequence = &et->as_sequence;    type->tp_as_mapping = &et->as_mapping;    type->tp_as_buffer = &et->as_buffer;

原来是把内建类型的几个方法集合放在了结构体后面而已。。。。

然后就是处理一些简单的属性比如tp_name什么的,都是简单的赋值

最后一个关键的地方就是slot的处理,这个slot和_slots_不一样,这个slot是python内部的东西,它处理了一个方法调用的走向。比如继承自内建type的类,复写其特殊方法,比如 继承int,复写 _add_,那么当做加法时,_add_会被调用,这是怎么实现的呢?

都在fixup_slot_dispatchers这个函数里面了。
这个函数遍历了slotdefs这个静态数组,

原创粉丝点击