微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么类 __dict__ 和 __weakref__ 从未在 Python 中重新定义?

如何解决为什么类 __dict__ 和 __weakref__ 从未在 Python 中重新定义?

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

>>> 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__','__dict__': <attribute '__dict__' of 'B' objects>,'__weakref__': <attribute '__weakref__' of 'B' objects>,'__doc__': None})

解决方法

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

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


您可以在 type_new 中看到实现,type.__new__ 的(很长)C 实现。查找 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_dictadd_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指针或weakref指针保留了空间,所以如果一个C祖先保留了空间没有提供描述符,则child 不会保留空间或提供描述符。例如,set 有一个非零的 tp_weaklistoffset,但没有 __weakref__ 描述符,所以 set 的后代也不会提供 __weakref__ 描述符,即使实例set 的(包括子类实例)支持弱引用。

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。