当前位置:   article > 正文

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

fixup_slot_dispatchers

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

字节码分析

先来个最简单的类:

class A:
    pass
  • 1
  • 2

编译一下:

              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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

可以看到第一条字节码就不认识了,好吧,暂时不管他是干啥的,往下看,接下来三条字节码创建了一个函数对象,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__);
  • 1

原来是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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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

然后获取基类列表:

bases = PyTuple_GetSlice(args, 2, nargs);
  • 1

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

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

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

然后调用了meta的_prepare_:

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

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

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

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

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

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

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

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

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

 return (PyObject *) Py_TYPE(x);
  • 1

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

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

__private =>  _classname__private
  • 1

然后赋值给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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

就是普通的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;
  • 1
  • 2
  • 3
  • 4
  • 5

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

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/199147
推荐阅读
相关标签
  

闽ICP备14008679号