I try to understand more thoroughly descriptor and explicit attribute name look-up order.
I read descriptor howto and it states as follows:
The details of invocation depend on whether obj is an object or a class:
...
For classes, the machinery is intype.__getattribute__
() which transformsB.x
intoB.__dict__['x'].__get__(None, B)
I test this on __class_
_ since it is a data descriptor of object
In [47]: object.__class__
Out[47]: type
So, it returns type
as expected since type
class creates all classes including object
class. Base on the 'descriptor howto', object.__class__
is turned into object.__dict__['__class__'].__get__(None, object)
.
However, When I run it, the output is the descriptor itself, not type
In [48]: object.__dict__['__class__'].__get__(None, object)
Out[48]: <attribute '__class__' of 'object' objects>
I guess it returns the descriptor itself because inside of this __get__
having some kind of code like:
if instance is None:
return self
So, I understand the reason of returning the descriptor itself when calling from class. The thing confuses me is the different ouputs
When it says 'B.x
into B.__dict__['x'].__get__(None, B)
', I expect outputs are the same. Why are they different?
Python __get__ Magic Method. Python's __get__() magic method defines the dynamic return value when accessing a specific instance and class attribute. It is defined in the attribute's class and not in the class holding the attribute (= the owner class).
Descriptors are Python objects that implement a method of the descriptor protocol, which gives you the ability to create objects that have special behavior when they're accessed as attributes of other objects.
“Descriptors” are objects that describe some attribute of an object. They are found in the dictionary of type objects.
In general, a descriptor is an object attribute with a binding behavior, one whose attribute access is overridden by methods in the descriptor protocol. Those methods are __get__ , __set__ , and __delete__ . If any of these methods are defined for an object, it is said to be a descriptor.
The descriptor how-to is a simplification. It glosses over things like metaclasses, and the fact that classes are objects. Classes are objects, and they go through both "object-style" and "class-style" attribute lookup and descriptor handling. (The implementation can be found in type_getattro
, if you want to independently verify this.)
A lookup for object.__class__
doesn't just go through object.__mro__
; it also looks through type(object).__mro__
. Descriptors found in type(object).__mro__
use "object-style" descriptor handling, treating the class as an instance of its metaclass, while descriptors found in object.__mro__
use "class-style" descriptor handling.
When you look up object.__class__
, Python searches through type(object).__mro__
. Since object
is in type(object).__mro__
, this search finds object.__dict__['__class__']
. Since object.__dict__['__class__']
is a data descriptor (it has a __set__
), this takes priority over the search through object.__mro__
. Thus, treating object
as an instance of object
rather than as a class, Python performs
descr.__get__(object, type(object))
instead of
descr.__get__(None, object)
and the __get__
call returns type(object)
, which is type
.
Your manual descr.__get__(None, object)
call treats object
as a class instead of as an instance of object
. Invoked this way, the descriptor returns itself.
To demonstrate that __class__
isn't being special-cased here, we can create our own class that's an instance of itself, just like object
is:
class DummyMeta(type):
pass
class SelfMeta(type, metaclass=DummyMeta):
@property
def x(self):
return 3
SelfMeta.__class__ = SelfMeta
print(SelfMeta.x)
print(SelfMeta.__dict__['x'].__get__(None, SelfMeta))
print(SelfMeta.__dict__['x'].__get__(SelfMeta, type(SelfMeta)))
Output:
3
<property object at 0x2aff9f04c5e8>
3
Just like with object.__class__
, "object-style" descriptor handling happens here too. (Also, in case you're wondering, properties are data descriptors even if you don't write a setter.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With