0

类创建似乎从不重新定义__dict____weakref__ 属性(即,如果它们已经存在于超类的字典中,则不会将它们添加到其子类的字典中),而是总是重新定义__doc____module__ 属性。为什么?

>>> class A: pass
... 
>>> class B(A): pass
... 
>>> class C(B): __slots__ = ()
... 
>>> vars(A)
mappingproxy({'__module__': '__main__',
              '__dict__': <attribute '__dict__' of 'A' objects>,
              '__weakref__': <attribute '__weakref__' of 'A' objects>,
              '__doc__': None})
>>> vars(B)
mappingproxy({'__module__': '__main__', '__doc__': None})
>>> vars(C)
mappingproxy({'__module__': '__main__', '__slots__': (), '__doc__': None})
>>> class A: __slots__ = ()
... 
>>> class B(A): pass
... 
>>> class C(B): pass
... 
>>> vars(A)
mappingproxy({'__module__': '__main__', '__slots__': (), '__doc__': None})
>>> vars(B)
mappingproxy({'__module__': '__main__',
              '__dict__': <attribute '__dict__' of 'B' objects>,
              '__weakref__': <attribute '__weakref__' of 'B' objects>,
              '__doc__': None})
>>> vars(C)
mappingproxy({'__module__': '__main__', '__doc__': None})
4

1 回答 1

1

类中的'__dict__'and'__weakref__'条目__dict__(如果存在)是用于从实例内存布局中检索实例的 dict 指针和 weakref 指针的描述符。它们不是实际的类__dict____weakref__属性——它们由元类上的描述符管理。

如果一个类的祖先已经提供了一个描述符,那么添加这些描述符是没有意义的。然而,一个类确实需要它自己的__module__and __doc__,不管它的父类是否已经有一个 - 类继承其父类的模块名称或文档字符串是没有意义的。


您可以在 中看到实现type_new,即 的(非常长的)C 实现type.__new__。寻找add_weakadd_dict变量——这些变量决定是否type.__new__应该在类的实例内存布局中__dict__添加空间。__weakref__如果type.__new__决定它应该为实例内存布局中的一个属性添加空间,它还会将 getset 描述符添加到类(通过tp_getset)以检索属性:

if (add_dict) {
    if (base->tp_itemsize)
        type->tp_dictoffset = -(long)sizeof(PyObject *);
    else
        type->tp_dictoffset = slotoffset;
    slotoffset += sizeof(PyObject *);
}
if (add_weak) {
    assert(!base->tp_itemsize);
    type->tp_weaklistoffset = slotoffset;
    slotoffset += sizeof(PyObject *);
}
type->tp_basicsize = slotoffset;
type->tp_itemsize = base->tp_itemsize;
type->tp_members = PyHeapType_GET_MEMBERS(et);

if (type->tp_weaklistoffset && type->tp_dictoffset)
    type->tp_getset = subtype_getsets_full;
else if (type->tp_weaklistoffset && !type->tp_dictoffset)
    type->tp_getset = subtype_getsets_weakref_only;
else if (!type->tp_weaklistoffset && type->tp_dictoffset)
    type->tp_getset = subtype_getsets_dict_only;
else
    type->tp_getset = NULL;

如果add_dictadd_weak为假,则不保留空间并且不添加描述符。add_dict为假或为假的一个条件add_weak是父母之一是否已经保留了空间:

add_dict = 0;
add_weak = 0;
may_add_dict = base->tp_dictoffset == 0;
may_add_weak = base->tp_weaklistoffset == 0 && base->tp_itemsize == 0;

此检查实际上并不关心任何祖先描述符,只关心祖先是否为实例 dict 指针或弱引用指针保留空间,因此如果 C 祖先保留空间而不提供描述符,则子节点将不会保留空间或提供描述符. 例如,set有一个 nonzero tp_weaklistoffset,但没有__weakref__描述符,因此后代 ofset也不会提供__weakref__描述符,即使set(包括子类实例)的实例支持弱引用。

您还将&& base->tp_itemsize == 0在初始化中看到may_add_weak- 您无法将弱引用支持添加到具有可变长度实例的类的子类。

于 2021-04-02T11:30:09.950 回答