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:
- When we call
a.__class__
, are we really calling the method type.__class__(a)
?
- Is this the reason why
__class__
is not a member of the __dict__
attribute of a
?
__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
.