Class creation seems to never re-define the __dict__ and __weakref__ class attributes (i.e. if they already exist in the dictionary of a superclass, they are not added to the dictionaries of its subclasses), but to always re-define the __doc__ and __module__ class attributes. Why?
>>> 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})
The
'__dict__'and'__weakref__'entries in a class's__dict__(when present) are descriptors used for retrieving an instance's dict pointer and weakref pointer from the instance memory layout. They're not the actual class's__dict__and__weakref__attributes - those are managed by descriptors on the metaclass.There's no point adding those descriptors if a class's ancestors already provide one. However, a class does need its own
__module__and__doc__, regardless of whether its parents already have one - it doesn't make sense for a class to inherit its parent's module name or docstring.You can see the implementation in
type_new, the (very long) C implementation oftype.__new__. Look for theadd_weakandadd_dictvariables - those are the variables that determine whethertype.__new__should add space for__dict__and__weakref__in the class's instance memory layout. Iftype.__new__decides it should add space for one of those attributes to the instance memory layout, it also adds getset descriptors to the class (throughtp_getset) to retrieve the attributes:If
add_dictoradd_weakare false, no space is reserved and no descriptor is added. One condition foradd_dictoradd_weakto be false is if one of the parents already reserved space:This check doesn't actually care about any ancestor descriptors, just whether an ancestor reserved space for an instance dict pointer or weakref pointer, so if a C ancestor reserved space without providing a descriptor, the child won't reserve space or provide a descriptor. For example,
sethas a nonzerotp_weaklistoffset, but no__weakref__descriptor, so descendants ofsetwon't provide a__weakref__descriptor either, even though instances ofset(including subclass instances) support weak references.You'll also see an
&& base->tp_itemsize == 0in the initialization formay_add_weak- you can't add weakref support to a subclass of a class with variable-length instances.