DEV Community

Cover image for __init__ Vs __new__ - When to Use and How
Sachin
Sachin

Posted on • Originally published at geekpython.in

__init__ Vs __new__ - When to Use and How

You must have seen the implementation of the __init__ method in any Python class, and if you have worked with Python classes, you must have implemented the __init__ method many times. However, you are unlikely to have implemented or seen a __new__ method within any class.

In this article, we'll see:

  • Definition of the __init__ and __new__ methods

  • __init__ method and __new__ method implementation

  • When they should be used

  • The distinction between the two methods

__init__ Vs __new__ Method

The __init__ method is an initializer method that is used to initialize the attributes of an object after it is created, whereas the __new__ method is used to create the object.

When we define both the __new__ and the __init__ methods inside a class, Python first calls the __new__ method to create the object and then calls the __init__ method to initialize the object's attributes.

Most programming languages require only a constructor, a special method to create and initialize objects, but Python has both a constructor and an initializer.

Let's talk about both these methods one by one and implement these methods inside a Python class.

__new__ Method

As stated already, the __new__ method is a constructor method used to create and return an object(instance of the class).

Syntax

object.__new__(cls, *args, **kwargs)

The __new__ method's first parameter is cls, which is a class of the object we want to create.

The *args and **kwargs parameters are not used by the __new__ method, but they must match the parameters of the class's __init__ method.

Understanding args and kwargs in Python: Best Practices and Guide

The (*) asterisk before the args ensures that the argument has a variable length. In **kwargs, double asterisk allows us to pass through keyword arguments.

favicon geekpython.in

Example

# Defined a base class
class Name:
    # Created a __new__ method
    def __new__(cls):
        print(f'Called the __new__ method.')
        return super(Name, cls).__new__(cls)

    # Created an __init__ method
    def __init__(self):
        print(f"Called the __init__ method.")

# Created an object
Name()
Enter fullscreen mode Exit fullscreen mode

In the above code, we defined the __new__ and __init__ methods within the class Name. The __new__ method accepts the cls parameter, which is used to refer to the class Name, and when called, it prints the message and returns the class instance using the super(Name, cls).__new__(cls).

One thing to note is that the Name class is a base class, so we could have directly called the __new__ method on the object like this expression object.__new__(cls). However, the standard method is to use the super() function.

How To Use super() Function Within Python Classes

The super() function extends the functionality of the superclass within the subclass or derived class.

favicon geekpython.in

The __init__ method is then called with the instance passed to the self parameter.

Then we called the Name class (Name()), and when we run the code, we get the output shown below.

Called the __new__ method.
Called the __init__ method.
Enter fullscreen mode Exit fullscreen mode

The output shows that the __new__ method is called first and then the __init__ method.

__init__ Method

As we saw in the above example, the __init__ method is called to initialize the attributes of the object as soon as the object is created.

__init__ and __call__ In Python - What Do They Do?

The __init__ method is used to initialize the object's attributes. Instances of classes can be made callable by defining a __call__ method in their class.

favicon geekpython.in

Syntax

__init__(self, *args, **kwargs)

As a first parameter, the __init__ method accepts self, which is used to refer to the class instance.

The parameters *args and **kwargs are used to initialize the instance variable with the values stored within them.

Example

# Defined a base class
class Name:
    # Created a __new__ method
    def __new__(cls, name):
        print(f'Called the __new__ method.')
        return super(Name, cls).__new__(cls)

    # Created an __init__ method
    def __init__(self, name):
        print(f"Called the __init__ method.")
        self.name = name

# Created an object
name_obj = Name('Sachin')
print(name_obj.name)
Enter fullscreen mode Exit fullscreen mode

In the __init__ method, we passed the name parameter and did the same in the __new__ method to make the __new__ and __init__ method signature compatible with each other.

We called the class with the 'Sachin' argument, which will automatically invoke the __init__ method and will initialize the instance variable self.name with this value.

When we call the name attribute(instance variable) on the object name_obj, we'll get the following output.

Called the __new__ method.
Called the __init__ method.
Sachin
Enter fullscreen mode Exit fullscreen mode

The name attribute(instance variable) of the name_obj is initialized to the value 'Sachin'.

Implementation

Let's define both __new__ and __init__ methods inside the class Language.

class Language:
    def __new__(cls, *args):
        return super().__new__(cls)


    def __init__(self, lang, year):
        self.lang = lang
        self.year = year


language = Language('Python', 1991)
print(language.lang)
print(language.year)

----------
Python
1991
Enter fullscreen mode Exit fullscreen mode

We defined both __new__ and __init__ methods inside the class Language and created the class object, when we run the code, Python will call the __new__ method which is responsible for creating and returning the object of the class, and then calls the __init__ method which is responsible for the initialization of the object's attributes(instance variables).

Now we can access the attributes of the object lang and year using dot notation on the object language as we did in the above code.

Every time we create a new object, the __init__ method is invoked, which means that if we don't return super().__new__(cls), then the __init__ method will not execute and return None.

class Language:
    def __new__(cls, *args):
        print("Creating")

    # Method not called
    def __init__(self, lang, year):
        print("Initializing")
        self.lang = lang
        self.year = year


language = Language('Python', 1991)
print(language)

----------
Creating
None
Enter fullscreen mode Exit fullscreen mode

Let's see what happens when we implement only the __init__ method inside a class.

class Language:
    def __init__(self, lang, year):
        self.lang = lang
        self.year = year


language = Language('Python', 1991)
print(language.lang)
print(language.year)

----------
Python
1991
Enter fullscreen mode Exit fullscreen mode

The code works the same as the previous code which we saw at the beginning of this section.

When we instantiated the class using language = Language('Python', 1991), the expression is equivalent to the following:

language = object.__new__(Language)
language.__init__('Python', 1991)
Enter fullscreen mode Exit fullscreen mode

If we try to print the language object after calling the __new__ and the __init__ methods using the __dict__, then we'll get the following output:

language = object.__new__(Language)
print(language.__dict__)
language.__init__('Python', 1991)
print(language.__dict__)

----------
{}
{'lang': 'Python', 'year': 1991}
Enter fullscreen mode Exit fullscreen mode

We got an empty dictionary after calling the __new__ method because the object was created but not yet initialized, to initialize, we called the __init__ method explicitly and got the values.

When To Use

Use case of __new__

Consider the following example in which we are using the __new__ method to customize the object at the instantiation.

class Reverse(str):
    def __new__(cls, sequence):
        return super().__new__(cls, sequence[::-1])

seq = Reverse("GeekPython")
print(seq)
Enter fullscreen mode Exit fullscreen mode

The above code defines the class Reverse, which inherits from the str built-in type, as well as the __new__ method that accepts a sequence. We override the __new__ method to reverse the sequence before creating the object.

nohtyPkeeG
Enter fullscreen mode Exit fullscreen mode

The argument "GeekPython" passed to the Reverse class got reversed due to sequence[::-1] before the object is created.

This can't be done using the __init__ method, if we try to do so, the result will be an error.

class Reverse(str):
    def __init__(self, sequence):
        super().__init__(sequence[::-1])

seq = Reverse("GeekPython")
print(seq)

----------
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
Enter fullscreen mode Exit fullscreen mode

Another use case of the __new__ method is creating a Singleton (design pattern that restricts the instantiation of a class to a single instance).

class Singleton:
    # Created a private variable
    __ins = None

    # Defined the __new__ method
    def __new__(cls):
        if cls.__ins is None:
            print("Instance creating...")
            cls.__ins = super().__new__(cls)
        return cls.__ins

# Creating object
obj1 = Singleton()
obj2 = Singleton()
obj3 = Singleton()
print(obj1)
print(obj2)
print(obj3)

# Checking if they are all same
print(obj1 is obj2 is obj3)
Enter fullscreen mode Exit fullscreen mode

In the above code, we defined the class Singleton and created a private variable __obj to store the class's single instance, as well as a __new__ method that checks if the __ins is None, then creates a new instance and assigns it to the __ins, and returns the existing instance if the __ins is not None.

Then we printed three instances of the Singleton class named obj1, obj2, and obj3 and checked to see if they were all the same.

Instance creating...
<__main__.Singleton object at 0x000001B3DFD5C130>
<__main__.Singleton object at 0x000001B3DFD5C130>
<__main__.Singleton object at 0x000001B3DFD5C130>
True
Enter fullscreen mode Exit fullscreen mode

All three instances point to the same memory address, and we can see that we got True, indicating that they are all the same.

Use case of __init__

The __init__ method is commonly used to initialize the object's attributes with or without the default values.

class Language:
    def __init__(self, lang="Python", year=1991):
        self.lang = lang
        self.year = year

    def show(self):
        print(f'Language: {self.lang} | Founded: {self.year}.')

language = Language()
language.show()
Enter fullscreen mode Exit fullscreen mode

The above code defines a Language class and the __init__ method, which accepts lang and year parameters with default values of "Python" and 1991, respectively.

When we call the Language class without argument, the __init__ method will set the lang and year attributes to their default values.

Language: Python | Founded: 1991.
Enter fullscreen mode Exit fullscreen mode

Difference

Now that we've seen the definition, syntax, and implementation of both methods, we are now able to differentiate between them.

__new__ method __init__ method
The __new__ method is called first The __init__ method is called after the __new__ method
Used to create and return the object Used to initialize the attributes of the object
It is a constructor method It is an initializer method
Takes class as the first parameter Takes the instance of the class as the first parameter
Can be overridden to customize the object at the instantiation Probably only be used to initialize the attributes of the object

Conclusion

Python has a concept of a constructor and an initializer method. The __new__ method is a constructor method whereas the __init__ method is an initializer method. Python first calls the __new__ method which is responsible for the object creation and then calls the __init__ method which is responsible for the initialization of the object's attributes.


🏆Other articles you might be interested in if you liked this one

Context managers and the with statement in Python.

What is abstract base class(ABC) in Python?

Public, Protected, and Private access modifiers in Python.

What are inheritance and different types of inheritance in Python?

What is enumerate() function in Python?

Execute dynamically generated code using the exec() in Python.

Async/Await - Asynchronous programming using asyncio in Python.


That's all for now

Keep Coding✌✌

Top comments (0)