Search code examples
pythoncallmetaclass

type object in Python


In Python, I reckon object(s) are available for the type class and the object name is same as class name which is type. I have a question regarding this... When we create classes using type(classname,base_class,att_dict), actually the call() method in type is getting invoked. This will eventually produce an object of the given class (eg: type("some_class", (),{}).

In this statement, type("some_class", (),{}), the type is the object (of the class type) Here, by using the type object the call() is invoked.

I reckon type() is a class method as well. So on this case, ideally it can be invoked by the following too:

type.__call__("some_class", (),{})

But this is erroring out.

TypeError: descriptor '__call__' requires a 'type' object but received a 'str'

At the same time, the below works well.

>>> class sample:
...     @classmethod
...     def __call__(cls,a):
...         return a*a
...
>>> sample.__call__(10)
100

So I am confused about the above error message.. like: where my understanding is incorrect? Can someone please help me.

Thanks


Solution

  • There are several parts that we need to discuss one by one.

    Your confusion starts from this sentence:

    I reckon type() (or __call__ more specifically) is a class method as well.

    No __call__ is a normal method (aka instance method). In instance methods if you call it from the class, you need to pass the first parameter. (This is the behavior of descriptor objects - methods are descriptors).

    With that being said, we have another thing which is special attribute lookup for magic methods (__call__ is a magic method.)

    Which says whenever you call type(something), Python is going to call it like: type(type).__call__(type, something). If you evaluate the first part(type of type is type) you'll end up with type.__call__(type, something).

    So:

    type("some_class", (), {})
    # or
    type.__call__(type, "some_class", (), {})
    

    Another thing that may be a bit obscure is that type is the instance of itself. That means when you put parenthesis in front of type, the __call__ of itself is going to be executed. "like a normal method".

    type metaclass   -->   custom class   -->   instance of the custom class
    
    
    type:
        instance "and" class of itself. class of the custom class
    custom class:
        instance of type. class of 'instance of the custom class'
    instance of the custom class:
        instance of the custom class.
    

    Note: If I could draw well, I would draw another arrow from 'type metaclass to again 'type metaclass'. It has this relationship with itself.

    Now you may ask: You said type is itself an instance so in type.__call__ aren't we calling it from an instance? why it's not bound?

    To answer that I highly recommend to take a look at this attribute lookup workflow. It found __call__ name in the type/class of the type(which is itself) but it's a non-data descriptor. So it checks the instance's namespace which is again type's namespace. It finds it and invokes it as it is. So no bounding behavior!


    This will eventually produce an object of the "given"(?) class

    Let's make it clear. This will eventually produce an object which is itself a class(a type) and its name is "some_class". This is the instance of the type metaclass.


    At the same time, the below works well.

    Yes because you deliberately made it a classmethod. The __call__ of the type metaclass is not a classmethod.

    Answer to the question in comment:

    Just to confirm my understanding... The 'type' class is NOT stored as object (like an user defined class is stored as an instance of 'type').. but 'type' class has a built-in instance called 'type' (same as its class name). am I correct ?

    No. There are no two distinct type objects in Python. type is an object (like any other objects in Python) which is both an instance of itself, "and" at the same time, it is the class of itself. There is one single type object. With is operator and id() you can confirm it:

    print(type is type(type))  # True
    
    id_of_type = id(type)
    id_of_type_of_type = id(type(type))
    print(id_of_type)          # 94651554023840
    print(id_of_type_of_type)  # 94651554023840