Python Dunder methods - __new__, __call__ and __init__

Rajesh PetheRajesh Pethe
3 min read

This write up deals with exploring the object creation aspect in Python. Specifically, it attempts to answer following queries:

  • How are Python objects created?

  • How can we change that for special use cases?

  • What magic takes place in this process? We are going to answer all these questions.

We'll take a deep dive in to dunder (double underscore) methods in Python, specially methods used in creation and initialization of objects.

Here is a normal class definition looks like:

    class MyClass:
        # attributes
        # and
        # method definitions here

        def __init__(self, *args, **kwarg):
            # Initialize *object* attributes

Lot of people thing that __init__ is a constructor but it is not the case. If you look closely, the first argument is self which is a convention for the object instance and not the class. You can name it whatever you want, but the first argument passed to __init__ is always the object instance. This means __init__ is not the constructor and the object is created before control reaches it.

So who is responsible to create the object instance? This is where __new__ comes in and is quite logical as new is named default constructor in most object oriented programming languages. In most cases, we do not need to define __new__ as the synopsis below suggests, it just creates an instance of class:

    def __new__(cls, *args, **kwargs):
        return super(Class_1, cls).__new__(cls, *args, **kwargs)

Note that it gets the class as its first argument and it calls the base constructor and returns an instance. This also implies that __new__ is a static method (or class method), it operates on class and not on instance.

But, does something else happens before constructor is called? We instantiate an object os a class via:

    instance = MyCLass()

If you notice, MyCLass() looks like a function call, yes? This is where another dunder method __call__ comes in. In short __call__ is invoked every time when the class is invoked as a function. And __call__ is also a static method and it also gets class as its first argument. Here is where you can control how an instance needs to be created or even if it needs to be created (singleton?).

Here is a snippet to understand the order in all of this object instantiation process:

Read more about metaclasses here

class Meta(type):
    def __call__(cls, *args, **kwargs):
        print(f"entering Meta.__call__(): {cls}")
        rv = super(Meta, cls).__call__(*args, **kwargs)
        print("exiting Meta.__call__()")
        return rv

class MyClass(metaclass=Meta):
    def __new__(cls, *args, **kwargs):
        print( "entering MyClass.__new__()")
        rv = super(MyClass, cls).__new__(cls, *args, **kwargs)
        print ("exiting MyClass.__new__()")
        return rv

    def __init__(self, *args, **kwargs):
        print ("executing MyClass.__init__()")
        super(MyClass,self).__init__(*args, **kwargs)

instance = MyClass() # Instantiate

The output shows everything is wrapped in __call__ method:

entering Meta.__call__(): <class '__main__.MyClass'>
entering MyClass.__new__()
exiting MyClass.__new__()
executing MyClass.__init__()
exiting Meta.__call__()

So __call__ is the best place to control how an object of that class is created. For example we can define a Singleton class which can be used as a metaclass of any class for which we want to create exactly one (single) instance.

    class Singleton(type):
        _instances = {}
        def __call__(cls, *args, **kwargs):
            if cls not in cls._instances:
                cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
            return cls._instances[cls]

Any class that declares Singleton as its metaclass will ever create only one instance.

    class Child(metaclass=Singleton):
        name = None
        def __init__(self, name):
            self.name = name

    child1 = Child('child1')
    child2 = Child('child2')

    assert child1 is child2

child1 is child2.

Conclusion

We can control object creation using __call__ method and do many other things by defining it in a metaclass, for example, creating a class based decorators. That, is for another time :).

0
Subscribe to my newsletter

Read articles from Rajesh Pethe directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rajesh Pethe
Rajesh Pethe

Passionate software engineer with 17+ years of experience in design and development of full life cycle commercial applications. Functional experience include Financial, Telecom and E-Commerce applications. Primary technical stack includes but not limited to Python, Django, REST, SQL, Perl, Unix/Linux. Secondary technical skills include Java, Angular and React JS. DevOps skills include CiCD, AWS, Docker, Kubernetes and Terraform.