Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

__get__ of descriptor __class__ of object class doesn't return as expected

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 in type.__getattribute__() which transforms B.x into B.__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?

like image 605
Andy L. Avatar asked Mar 23 '19 08:03

Andy L.


People also ask

What is __ get __ in Python?

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).

What is descriptor class in Python?

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.

What is a descriptor object?

“Descriptors” are objects that describe some attribute of an object. They are found in the dictionary of type objects.

What is method descriptor in Python?

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.


1 Answers

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.)

like image 178
user2357112 supports Monica Avatar answered Sep 21 '22 04:09

user2357112 supports Monica