__dict__ 和 __weakref__ 是 class 定义中的默认插槽吗?

Are __dict__ and __weakref__ the default slots at class definition?

根据 Python language documentation:

__slots__ allow us to explicitly declare data members (like properties) and deny the creation of __dict__ and __weakref__ (unless explicitly declared in __slots__ or available in a parent.)

所以我想知道这个 class

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

相当于这个:

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

如果您不设置 __slots__ 属性,默认情况下会在特定条件下创建 __weakref____dict__ 插槽。

这发生在 type.__new__(); a class 被赋予描述符以访问每个实例的内存槽:

  • 首先,通过测试类型对象结构中的 tp_dictoffset, tp_weaklistoffset and tp_itemsize 值来检查类型 是否可以 具有这些插槽。

    • 如果基数 class 尚未定义(base->tp_dictoffset 为 0),则只能有一个 __dict__ 槽。
    • 如果基本类型没有定义(base->tp_weaklistoffset 为 0)并且基本具有固定长度的实例(base->tp_itemsize 为 0),则只能有一个 __weakref__ 槽).
  • 然后,如果没有 __slots__,则设置标志变量以实际启用正在添加的插槽。

type.__new__() implementation 很长,但相关部分如下所示:

    slots = _PyDict_GetItemIdWithError(dict, &PyId___slots__);
    nslots = 0;
    add_dict = 0;
    add_weak = 0;
    may_add_dict = base->tp_dictoffset == 0;
    may_add_weak = base->tp_weaklistoffset == 0 && base->tp_itemsize == 0;
    if (slots == NULL) {
        if (PyErr_Occurred()) {
            goto error;
        }
        if (may_add_dict) {
            add_dict++;
        }
        if (may_add_weak) {
            add_weak++;
        }
    }
    else {
        /* Have slots */
        ...
    }

然后,往下约 400 行,您会找到实际创建插槽的代码:

    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 *);
    }

当扩展某些本机类型(例如 intbytes)时,您不会获得 __weakref__ 插槽,因为它们具有可变长度实例(由 [=18 处理) =]).