Object  instantiation process in Python

Object instantiation process in Python

Demystify __new__ in python👽

·

10 min read

In this article, we will cover the object instantiation process used in python to create objects.

In this article you will learn:

  • What is an object in python
  • Object instantiation process in Python
  • The __new__ method
  • The __call__ method
  • Callable in python

Python is an Object-oriented programming language. Everything in python is an object or instance, such as class, function, integer, float, etc. So each object has a class from which it is instantiated. We can use type(obj) function or __class__ property to check the type of the object.

>>> type(a)
<class 'int'>
>>> a.__class__
<class 'int'>

In python, all classes inherits from the object (built-in) class and this object class provides some common methods such as __new__, __init__, __doc__, __dir__ etc.

>>> dir(object)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

In python, Classes are themselves objects of class type.
Note: This is different from the type function. It is a metaclass from which all the classes are created.

>>> class Vehicle:
...    pass
... 
>>> veh = Vehicle()
>>> type(veh)
<class '__main__.Vehicle'>
>>> type(Vehicle)
<class 'type'>

Object instantiation process in Python:


  • 👉Object Initialization in Python is a two-step process. Whenever a class is instantiated __new__ and __init__ methods are called. __new__ method will be called to create a new object, as an instance of the desired class (object creation), and __init__ method will be called to initialize the object (object initialization).
  • 👉__new__ is a static method that makes sense since we are yet to create the instance.

  • 👉__new__ method takes the class of which an instance was requested as its first argument(cls or class reference) and the additional arguments we pass the call to class. example: name, age, etc.

  • 👉As per python official doc: Typical implementations create a new instance of the class by invoking the superclass’s __new__() method using super().__new__(cls[, ...]) (same as object.__new__(cls, ...) since object is the parent class of all classes with appropriate arguments and then modifying the newly-created instance as necessary before returning it.
class Person:
    def __new__(cls, name, age):
        print(f'Creating instance {cls.__name__} with args {name}, {age}')
        instance = super().__new__(cls)  # delegate to object.__new__

        # don't forget to return the new instance!, otherwise 
        # init won't be called
        return instance  

    def __init__(self, name, age):
        print('Initializing instance...', name, age)
        self.name =  name
        self.age = age

if __name__=='__main__':
    p = Person('viscabar', 25)

if __name__=='__main__':
    p = Person('viscabar', 25)

>>>
Creating instance Person with args viscabar, 25
Initializing instance... viscabar 25

Note: We must call the object class __new__ method inside the overridden __new__ method to create the object and allocate memory to the object.

Here inside __new__ method of Person class, we are calling object class __new__ class and this will create an instance of our desired class (Person class) and returns it.

🙄Till now, you must have thought about why to override the __new__ as you can directly do this using only __init__. Basically, It allows us to tweak how the class is created and so use it to initialize the object or modify it as required before returning it as shown below:

class Person:
    def __new__(cls, age):
        print(f'Creating instance {cls.__name__} with args: {age}')
        instance = super().__new__(cls)  # delegate to object.__new__
        instance.name = 'viscabar'

        # don't forget to return the new instance!, otherwise 
        # init won't be called
        return instance  

    def __init__(self, age):
        print('Initializing instance...', age)
        self.age = age

if __name__=='__main__':
    p = Person(25)
    print(p.name)

>>>
Creating instance Person with args: 25
Initializing instance... 25
viscabar

Here the __new__ method of the Person class modifies the obj returned from the __new__ method of the object class and adds the name property to it. Thus, all objects created using the Person class will have a name property with the name 'viscabar'.

But that's not the only thing that happens - the __init__ is also automatically called right after. But only if the type returned by __new__ matches the type specified as the first argument of __new__ (cls or class reference or in our case Person type)😮.

class Person:
    def __new__(cls, name):
        print(f'Creating instance of {cls.__name__}... not really...')
        instance = str(name)
        return instance

    def __init__(self, name):
        print('Init called...')
        self.name = name

if __name__=='__main__':
    p = Person('viscabar')
    print(p, type(p))

>>>
Creating instance of Person... not really...
viscabar <class 'str'>

As you can see the __init__ was not called and that makes sense since __new__ is not returning an instance of Person (currently it is returning an instance of str) so, it does not make sense to invoke the__init__ for Person, nor for the newly created instance.

All the methods that are started with a double underscore (dunder) and end with a double underscore are known as magic methods🔮. These are called implicitly by python as we don't need to call them explicitly.

__call__ method:


So I guess till now you must have thought about this:
How does python calls the __new__ and __init__ automatically?
In python classes are callable. and callable objects are objects that can be called.You can use callable(obj_reference) function to check if the object is callable or not.

>>> callable(object)
True
>>> callable(Person)
True
>>> a=10
>>> callable(a)
False

In python, __call__ method is a magic method that is used to make the objects callable. In Python, class is also a callable object. All classes in python are of type class therefore the type class must have a __call__ method. Hence, when we call Person instance p(), in the background, Python calls the __call__ method of the type class.

class Person:
    def __new__(cls, name):
        print(f'Creating instance of {cls.__name__}.')
        instance = super().__new__(cls)
        return instance

    def __init__(self, name):
        print('Init called...')
        self.name = name

    def __call__(cls):
        print('Inside __call__ method')

if __name__=='__main__':
    p = Person('viscabar')
    p()

>>>
Creating instance of Person.
Init called...
Inside __call__ method

as we can see that Person instance is now callable since we have defined the __call__ method.

Below is the definition of type class __call__ method. it is not exactly the same since it is originally written in C language but below is the version of python how it should look like :

def __call__(cls, *args, **kwargs):
    p = cls.__new__(*args, **kwargs)

    # After __new__ method returns the object, __init__ method will only be called if
    # p is not None
    # p is an instance of class Person
    # __init__ method is defined on the instance p class
    if p is not None and isinstance(p, cls) and hasattr(p, '__init__'):
        # As __init__ is called on p, self will be equal to p in __init__ method
        p.init(*args, **kwargs)

    return p

Conclusion:


I hope this will clarify the object creation and instantiation processes in Python. We also explored some of the magic methods (__init__, __new__, __call__) in python.😀😀😀

Did you find this article valuable?

Support viscabar by becoming a sponsor. Any amount is appreciated!