Understanding __new__ and __init__

The __new__ method is responsible for creating a new instance of a class. It is a static method that takes the class as the first argument and returns an instance of that class (or a subclass). In contrast, the __init__ method is an instance method used to initialize the newly created instance.

Here is a basic example to illustrate the flow:

class MyClass:
    def __new__(cls, *args, **kwargs):
        print("Creating instance")
        instance = super().__new__(cls)
        return instance

    def __init__(self, value):
        print("Initializing instance")
        self.value = value

obj = MyClass(10)

Output:

Creating instance
Initializing instance

In this example, __new__ creates the instance, and __init__ sets up its attributes.

Key Differences

Feature__new____init__
PurposeCreate the instanceInitialize the instance
Method typeStatic methodInstance method
Return valueReturns the new instanceNo return value
Calling orderCalled before __init__Called after __new__
Usage in subclassingCan control the type of the objectCannot change the type of the instance

Practical Use Cases

1. Enforcing Immutable Types

When subclassing immutable types like int, str, or tuple, you must override __new__ because the instance is created at that point. Here's an example that creates a specialized string:

class UppercaseString(str):
    def __new__(cls, value):
        value = value.upper()
        return super().__new__(cls, value)

s = UppercaseString("hello")
print(s)  # HELLO

2. Implementing a Singleton Pattern

You can use __new__ to ensure only one instance of a class is ever created:

class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, value):
        self.value = value

s1 = Singleton(10)
s2 = Singleton(20)

print(s1.value)  # 10
print(s2.value)  # 20
print(s1 is s2)  # True

Note that __init__ is still called every time, so the value might be overwritten if not handled carefully.

3. Factory Pattern Based on Input

You can use __new__ to return different types of objects based on input:

class ShapeFactory:
    def __new__(cls, shape_type):
        if shape_type == 'circle':
            return Circle()
        elif shape_type == 'square':
            return Square()
        else:
            raise ValueError("Unknown shape type")

class Circle:
    def draw(self):
        print("Drawing a circle")

class Square:
    def draw(self):
        print("Drawing a square")

shape = ShapeFactory('circle')
shape.draw()  # Drawing a circle

Best Practices

  • Prefer __init__ for initialization: Use __new__ only when you need to control the creation process, such as for immutable types or singleton patterns.
  • Always call the parent's __new__ method: When overriding __new__, use super().__new__(cls) to ensure proper inheritance and object creation.
  • Be cautious with __init__ in __new__-based classes: Since __init__ is always called after __new__, you must ensure it does not interfere with object identity or singleton logic.
  • Document the behavior clearly: When using __new__, it's important to document how object creation works, especially for other developers using or subclassing your class.

Learn more with useful resources