The names __class__ and __name__ are special. Both are data descriptors. __name__ defined in type object, __class__ is defined in object (base class of all classes of the new style):
>>> type.__dict__['__name__'] <attribute '__name__' of 'type' objects> >>> type.__dict__['__name__'].__get__ <method-wrapper '__get__' of getset_descriptor object at 0x1059ea870> >>> type.__dict__['__name__'].__set__ <method-wrapper '__set__' of getset_descriptor object at 0x1059ea870> >>> object.__dict__['__class__'] <attribute '__class__' of 'object' objects> >>> object.__dict__['__class__'].__get__ <method-wrapper '__get__' of getset_descriptor object at 0x1059ea2d0> >>> object.__dict__['__class__'].__set__ <method-wrapper '__set__' of getset_descriptor object at 0x1059ea2d0>
Since they are data descriptors, the type.__getattribute__ (used to access attributes in the class) ignores any attributes set in the __dict__ class and only use the descriptors themselves:
>>> type.__getattribute__(Foo, '__class__') <class 'type'> >>> type.__getattribute__(Foo, '__name__') 'Foo'
Fun fact: type comes from object (everything in Python is an object), so __class__ is on type when checking data descriptors:
>>> type.__mro__ (<class 'type'>, <class 'object'>)
( type.__getattribute__(D, ...) used directly as an unrelated method, not D.__getattribute__() , because all special access methods are of type ).
See Descriptor Howto what a data descriptor is and why it matters:
If an object defines both __get__() and __set__() , it is considered a data descriptor. Descriptors that define only __get__() are called descriptors without data (they are usually used for methods, but other uses are possible).
Data and data descriptors other than data differ in how overrides are calculated for entries in the instance dictionary. If the instance dictionary has an entry with the same name as the data descriptor, the data descriptor takes precedence. If the instance dictionary has an entry with the same name as the non-data descriptor, the dictionary entry takes precedence.
For data descriptors in type class is another instance.
Therefore, when searching for the attributes __class__ or __name__ does not matter what is defined in the namespace D.__dict__ , because for the data descriptor in the namespace formed by type , this is MRO.
These descriptors are defined in typeobject.c C code :
static PyGetSetDef type_getsets[] = { {"__name__", (getter)type_name, (setter)type_set_name, NULL}, } PyTypeObject PyType_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "type", type_getsets, } static PyGetSetDef object_getsets[] = { {"__class__", object_get_class, object_set_class, PyDoc_STR("the object class")}, {0} }; PyTypeObject PyBaseObject_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "object", object_getsets, }
The instances use object.__getattribute__ and it will find the __name__ and __class__ in the D.__dict__ before it finds the data descriptors in object or type .
However, if you omit, then the name lookup on D() will only __class__ as the data descriptor in MRO D (so, on object ). __name__ not found, because metatypes are not taken into account when resolving instance attributes.
That way you can set __name__ in the instance, but not __class__ :
>>> class E: pass ... >>> e = E() >>> e.__class__ <class '__main__.E'> >>> e.__name__ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'E' object has no attribute '__name__' >>> e.__dict__['__class__'] = 'ignored' >>> e.__class__ <class '__main__.E'> >>> e.__name__ = 'this just works' >>> e.__name__ 'this just works'