Class lookup rule?

>>> class D: ... __class__ = 1 ... __name__ = 2 ... >>> D.__class__ <class 'type'> >>> D().__class__ 1 >>> D.__name__ 'D' >>> D().__name__ 2 

Why D.__class__ return the class name, and D().__class__ returns the specified attribute in class D?

And where did the built-in attributes like __class__ and __name__ come from?

I suspected that __name__ or __class__ are simple descriptors that live either in the object class or somewhere, but this is not visible.

In my understanding, attribute search rules follow in Python, omitting the conditions for descriptors, etc.:

Instance --> Class --> Class.__bases__ and the bases of the other classes as well

Given the fact that the class is an instance of the metaclass, type in this case, why is D.__class__ not looking for __class__ in D.__dict__ ?

+5
source share
1 answer

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}, /* ... several more ... */ } /* ... */ PyTypeObject PyType_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "type", /* tp_name */ /* ... many type definition entries ... */ type_getsets, /* tp_getset */ /* ... many type definition entries ... */ } /* ... */ 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", /* tp_name */ /* ... many type definition entries ... */ object_getsets, /* tp_getset */ /* ... many type definition entries ... */ } 

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' 
+8
source

All Articles