6

In Python's documentation __class__ is described as an attribute. In the object type (the metaclass), __class__ appears to be a method.

If we do:

>>> class Foo:
        pass

>>> a = Foo()
>>> a.__class__ == type.__class__(a)
True

So, my questions are:

  1. When we call a.__class__, are we really calling the method type.__class__(a)?
  2. Is this the reason why __class__ is not a member of the __dict__ attribute of a?
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
max fraguas
  • 438
  • 1
  • 6
  • 12

1 Answers1

17

__class__ is a data descriptor object. Many attributes on Python core objects are implemented as descriptors. You should see that as an implementation detail, nothing more.

__class__ is a descriptor because Python needs to be able to validate new values you assign to it; there are certain limitations to assigning to __class__ that need to be honoured, and making __class__ a descriptor is the most efficient method of doing so.

Descriptor objects are automatically found and invoked on the type when you try to access the attribute on an object. instance.__class__ will find and execute the __class__ descriptor on the class (by searching through all classes in the inheritance graph), typically ending up with object.__dict__['__class__'].__get__(instance, type(instance)) (where object is usually the first class on which the __class__ attribute is found in the type(instance).__mro__ sequence); This happens because Python will always use the type's __getattribute__ method to find attributes, and that method knows how to handle descriptors found on the class and bases, as well as look at the object.__dict__ attributes. So they don't live on the object __dict__ itself, they live on the object type, by design.

Now, class objects are also callable objects. That's how you create an instance; for a given class Foo, you create an instance by calling it, so Foo(). instance.__class__ is just a reference to the class object, just like class_obj = Foo would create a reference to a class. Calling the class object produces a new instance, whatever reference you used to get to it.

Finally, type.__class__ is just a reference to type() itself:

>>> type.__class__ is type
True
>>> type.__class__
<class 'type'>
>>> type(type)
<class 'type'>

That's because type is its own type. The parent-child relationships of the Python type system must stop somewhere, and type is that point.

In your example, a.__class__ is a reference to the Foo class. And type.__class__ is the same object as type, so you essentially did this:

Foo == type(a)

which is indeed true, the type of a is Foo.

OverLordGoldDragon
  • 1
  • 9
  • 53
  • 101
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 2
    Thank you for your answer. Now, you said: `instance.__class__ ` will find and execute `type(instance).__dict__['__class__'].__get__(instance, type(instance))` but if do: `type(instance).__dict__` there is not a **__ class__** descriptor, which gets me to my first question, and assume that the descriptor **__ class__** exist only in the object **type** and is passed to all their instance objects (all classes). ? – max fraguas Jan 04 '18 at 04:37
  • 2
    @maxfraguas: sorry, I made things a little more confusing than I meant to. It's `object.__dict__['__class__']` that is the descriptor object; all classes inherit from that. The whole class hierarchy is searched for such attributes (so `next(t.__dict__['__class__'] for t in type(instance).__mro__ if '__class__' in t.__dict__).__get__(instance, type(instance))`. – Martijn Pieters Jan 04 '18 at 18:32
  • Thanks @Matijn Pieters. After a little of playing around, I found `__class__` inside the **object**'s `__dict__`. – max fraguas Jan 05 '18 at 05:08
  • Can this method be overriden? – Lex Podgorny Dec 04 '22 at 15:17
  • 3
    @LexPodgorny: It's not a method, it's a data descriptor. Yes, you can create your own `__class__` data descriptor, e.g. by using a property with a setter. – Martijn Pieters Dec 07 '22 at 21:26