【问题标题】:Why are the class __dict__ and __weakref__ never re-defined in Python?为什么类 __dict__ 和 __weakref__ 从未在 Python 中重新定义?
【发布时间】:2021-06-29 04:30:11
【问题描述】:

类创建似乎永远不会重新 - 定义__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__', '__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})

【问题讨论】:

    标签: python class attributes python-datamodel


    【解决方案1】:

    类的__dict__(如果存在)中的'__dict__''__weakref__' 条目是用于从实例内存布局中检索实例的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 指针或弱引用指针保留空间,因此如果 C 祖先保留空间没有提供描述符,则child 不会保留空间或提供描述符。例如,set 有一个非零的tp_weaklistoffset,但没有__weakref__ 描述符,所以set 的后代也不会提供__weakref__ 描述符,即使set 的实例(包括子类实例)支持弱引用。

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

    【讨论】:

    • @Maggyero:你看到的int 是另一回事——看到may_add_weak 行中的&amp;&amp; base-&gt;tp_itemsize == 0int 具有可变大小的实例,而不是像 list 那样通过固定大小实例中的单独数据指针 - int 实例是单个可变长度的内存块。您不能将弱引用支持添加到可变长度类型的子类。有一些特殊处理支持添加__dict__,但不支持添加__weakref__
    • 哦,对了,type 中没有 __weakref__ 描述符 - 它有一个非零的弱列表偏移量,但没有通过属性提供访问的描述符,例如 set 示例。
    • 实际上有__doc____module__ 的描述符,但是这些描述符存在于type 中,它们之所以存在是因为C 类不将它们的文档字符串和模块名称存储在它们的字典中。普通类拥有__doc____module__ 描述符是没有意义的——即使您将文档字符串和模块名称移动到C 类使用的内存插槽中,将描述符放在@987654369 中也会更有意义@负责提供访问权限。
    • 您可以尝试将描述符放入object 并依赖owner 参数__get__ 接受,但__set____delete__ 不起作用。
    • 我不会说这是描述符的本质。碰巧子类不需要覆盖那些特定的东西。有很多描述符子类 do 覆盖 - 例如,每个被覆盖的方法 - 并且大量非描述符子类不会覆盖。
    猜你喜欢
    • 2021-07-02
    • 2011-06-20
    • 2016-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-21
    相关资源
    最近更新 更多