大师兄的Python源码学习笔记(二十四): 虚拟机中的类机制(三)

大师兄的Python源码学习笔记(二十三): 虚拟机中的类机制(二)
大师兄的Python源码学习笔记(二十五): 虚拟机中的类机制(四)

二. 从type对象到class对象

3. 填充tp_dict
3.2 确定MRO
  • 由于Python支持多重继承,因此在多重继承时需要判断以何种顺序解析属性。
  • Method Resolve Order(MRO)即Class对象属性的解析顺序。
>>>class A(list):
>>>    def show(self):
>>>        print("A::show")
>>>
>>>class B(list):
>>>    def show(self):
>>>        print("B::show")
>>>
>>>class C(A):
>>>    pass
>>>
>>>class D(C,B):
>>>    pass
>>>
>>>if __name__ == '__main__':
>>>    d = D()
>>>    d.show()
A::show
  1           0 LOAD_BUILD_CLASS
              2 LOAD_CONST               0 (<code object A at 0x0000021737C39150, file "D:\pythonProject\parser_learn\demo.py", line 1>)
              4 LOAD_CONST               1 ('A')
              6 MAKE_FUNCTION            0
              8 LOAD_CONST               1 ('A')
             10 LOAD_NAME                0 (list)
             12 CALL_FUNCTION            3
             14 STORE_NAME               1 (A)

  5          16 LOAD_BUILD_CLASS
             18 LOAD_CONST               2 (<code object B at 0x0000021737C39030, file "D:\pythonProject\parser_learn\demo.py", line 5>)
             20 LOAD_CONST               3 ('B')
             22 MAKE_FUNCTION            0
             24 LOAD_CONST               3 ('B')
             26 LOAD_NAME                0 (list)
             28 CALL_FUNCTION            3
             30 STORE_NAME               2 (B)

  9          32 LOAD_BUILD_CLASS
             34 LOAD_CONST               4 (<code object C at 0x0000021737C39540, file "D:\pythonProject\parser_learn\demo.py", line 9>)
             36 LOAD_CONST               5 ('C')
             38 MAKE_FUNCTION            0
             40 LOAD_CONST               5 ('C')
             42 LOAD_NAME                1 (A)
             44 CALL_FUNCTION            3
             46 STORE_NAME               3 (C)

 12          48 LOAD_BUILD_CLASS
             50 LOAD_CONST               6 (<code object D at 0x0000021737C395D0, file "D:\pythonProject\parser_learn\demo.py", line 12>)
             52 LOAD_CONST               7 ('D')
             54 MAKE_FUNCTION            0
             56 LOAD_CONST               7 ('D')
             58 LOAD_NAME                3 (C)
             60 LOAD_NAME                2 (B)
             62 CALL_FUNCTION            4
             64 STORE_NAME               4 (D)

 15          66 LOAD_NAME                5 (__name__)
             68 LOAD_CONST               8 ('__main__')
             70 COMPARE_OP               2 (==)
             72 POP_JUMP_IF_FALSE       88

 16          74 LOAD_NAME                4 (D)
             76 CALL_FUNCTION            0
             78 STORE_NAME               6 (d)

 17          80 LOAD_NAME                6 (d)
             82 LOAD_METHOD              7 (show)
             84 CALL_METHOD              0
             86 POP_TOP
        >>   88 LOAD_CONST               9 (None)
             90 RETURN_VALUE
  • 这里实际是通过PyType_Ready中通过mro_internal函数完成了对一个类型的mro顺序的建立:
Objects\typeobject.c

int
PyType_Ready(PyTypeObject *type)
{
    ... ...

    /* Calculate method resolution order */
    if (mro_internal(type, NULL) < 0)
        goto error;
    ... ...
}
Objects\typeobject.c

static int
mro_internal(PyTypeObject *type, PyObject **p_old_mro)
{
    PyObject *new_mro, *old_mro;
    int reent;

    /* Keep a reference to be able to do a reentrancy check below.
       Don't let old_mro be GC'ed and its address be reused for
       another object, like (suddenly!) a new tp_mro.  */
    old_mro = type->tp_mro;
    Py_XINCREF(old_mro);
    new_mro = mro_invoke(type);  /* might cause reentrance */
    reent = (type->tp_mro != old_mro);
    Py_XDECREF(old_mro);
    if (new_mro == NULL)
        return -1;

    if (reent) {
        Py_DECREF(new_mro);
        return 0;
    }

    type->tp_mro = new_mro;

    type_mro_modified(type, type->tp_mro);
    /* corner case: the super class might have been hidden
       from the custom MRO */
    type_mro_modified(type, type->tp_bases);

    PyType_Modified(type);

    if (p_old_mro != NULL)
        *p_old_mro = old_mro;  /* transfer the ownership */
    else
        Py_XDECREF(old_mro);

    return 1;
}
  • Python虚拟机将创建一个tuple对象,按照虚拟机解析属性时的mro顺序存放class对象,并将这个tuple对象存放在PyTypeObjecttp_mro中:
Include\object.h

typedef struct _typeobject {
    ... ...
    PyObject *tp_mro; /* method resolution order */
    ... ...
} PyTypeObject;
  • 可以在Python中通过Class.__mro__函数查看tp_mro
>>>class A(list):
>>>    def show(self):
>>>        print("A::show")
>>>
>>>class B(list):
>>>    def show(self):
>>>        print("B::show")
>>>
>>>class C(A):
>>>    pass
>>>
>>>class D(C,B):
>>>    pass
>>>
>>>if __name__ == '__main__':
>>>    print(D.__mro__)
(<class '__main__.D'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'list'>, <class 'object'>)
3.3 继承基类操作
  • Python虚拟机确定mro列表后,会遍历mro列表
  • 由于第一项总是其自身,所以遍历会从第二项开始。
  • mro列表中存储的是Class对象的所有直接和间接基类,虚拟机会将Class对象自身没有设置而积累中设置了的操作复制到Class对象中,从而完成基类操作的继承动作:
Objects\typeobject.c

int
PyType_Ready(PyTypeObject *type)
{
    ... ...
    /* Initialize tp_dict properly */
    bases = type->tp_mro;
    assert(bases != NULL);
    assert(PyTuple_Check(bases));
    n = PyTuple_GET_SIZE(bases);
    for (i = 1; i < n; i++) {
        PyObject *b = PyTuple_GET_ITEM(bases, i);
        if (PyType_Check(b))
            inherit_slots(type, (PyTypeObject *)b);
    }
}
Objects\typeobject.c

static void
inherit_slots(PyTypeObject *type, PyTypeObject *base)
{
    PyTypeObject *basebase;

#undef SLOTDEFINED
#undef COPYSLOT
#undef COPYNUM
#undef COPYSEQ
#undef COPYMAP
#undef COPYBUF

#define SLOTDEFINED(SLOT) \
    (base->SLOT != 0 && \
     (basebase == NULL || base->SLOT != basebase->SLOT))

#define COPYSLOT(SLOT) \
    if (!type->SLOT && SLOTDEFINED(SLOT)) type->SLOT = base->SLOT

#define COPYASYNC(SLOT) COPYSLOT(tp_as_async->SLOT)
#define COPYNUM(SLOT) COPYSLOT(tp_as_number->SLOT)
#define COPYSEQ(SLOT) COPYSLOT(tp_as_sequence->SLOT)
#define COPYMAP(SLOT) COPYSLOT(tp_as_mapping->SLOT)
#define COPYBUF(SLOT) COPYSLOT(tp_as_buffer->SLOT)

    /* This won't inherit indirect slots (from tp_as_number etc.)
       if type doesn't provide the space. */

    if (type->tp_as_number != NULL && base->tp_as_number != NULL) {
        basebase = base->tp_base;
        if (basebase->tp_as_number == NULL)
            basebase = NULL;
        COPYNUM(nb_add);
        COPYNUM(nb_subtract);
        COPYNUM(nb_multiply);
        COPYNUM(nb_remainder);
        ... ...
}
3.4 填充基类中的子类列表
  • 最后,PyType_Ready还要设置基类中的子类列表。
  • 在每一个PyTypeObject中的tp_subclasses是一个list对象,其中存放着所有直接继承自该类型的class对象。
  • PyType_Ready通过调用add_subclass完成向tp_subclasses中填充对象的动作。
Objects\typeobject.c

int
PyType_Ready(PyTypeObject *type)
{
    PyObject *dict, *bases;
    PyTypeObject *base;
    Py_ssize_t i, n;

    ... ...

    /* Link into each base class's list of subclasses */
    bases = type->tp_bases;
    n = PyTuple_GET_SIZE(bases);
    for (i = 0; i < n; i++) {
        PyObject *b = PyTuple_GET_ITEM(bases, i);
        if (PyType_Check(b) &&
            add_subclass((PyTypeObject *)b, type) < 0)
            goto error;
    }

    ... ...
}
Objects\typeobject.c

static int
add_subclass(PyTypeObject *base, PyTypeObject *type)
{
    int result = -1;
    PyObject *dict, *key, *newobj;

    dict = base->tp_subclasses;
    if (dict == NULL) {
        base->tp_subclasses = dict = PyDict_New();
        if (dict == NULL)
            return -1;
    }
    assert(PyDict_CheckExact(dict));
    key = PyLong_FromVoidPtr((void *) type);
    if (key == NULL)
        return -1;
    newobj = PyWeakref_NewRef((PyObject *)type, NULL);
    if (newobj != NULL) {
        result = PyDict_SetItem(dict, key, newobj);
        Py_DECREF(newobj);
    }
    Py_DECREF(key);
    return result;
}
  • 在Python中可以通过class.__subclass__查看tp_subclasses
>>>class A(list):
>>>    def show(self):
>>>        print("A::show")
>>>
>>>class B(list):
>>>    def show(self):
>>>        print("B::show")
>>>
>>>class C(A):
>>>    pass
>>>
>>>class D(C,B):
>>>    pass
>>>
>>>if __name__ == '__main__':
>>>    print(C.__subclasses__())
[<class '__main__.D'>]
4. 总结
  • 总结PyType_ReadyPyTypeObject进行的改造:
  1. 设置type信息、基类及基类列表
  2. 填充tp_dict
  3. 确定mro列表
  4. 基于mro列表从基类继承操作
  5. 设置基类的子类列表
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容