Fundamentals 13 min read

What Really Creates Python Objects? Unveiling __new__ vs __init__

This article explains how Python actually creates objects, clarifying the distinct roles of the __new__ and __init__ methods, and demonstrates practical techniques such as inheriting immutable types, adding metadata, and implementing singleton patterns through custom object creation.

Code Mala Tang
Code Mala Tang
Code Mala Tang
What Really Creates Python Objects? Unveiling __new__ vs __init__

Ever wondered whether

__init__

is a constructor? In Python, object creation is split into two steps:

__new__

allocates memory and returns a new instance, while

__init__

initializes that instance’s state.

1. Theory: Object creation in Python

Python creates objects internally before calling the initializer. Understanding this mechanism lets you manipulate object creation for advanced use‑cases.

How to create objects in Python?

You simply instantiate a class, e.g.

SimpleObject(name="bob")

, or create built‑in types like

str

or

int

. The following class defines an

__init__

method and a

say_hello

method:

<code>class SimpleObject:
    greet_name: str
    def __init__(self, name: str):
        self.greet_name = name
    def say_hello(self) -> None:
        print(f"Hello {self.greet_name}!")

my_instance = SimpleObject(name="bob")
my_instance.say_hello()
</code>

Note that

__init__

receives the

name

argument and stores it in the

greet_name

attribute, preserving the instance’s state.

__init__ is a constructor?

Technically, no. A constructor creates the object;

__init__

only sets its state after the object has already been created.

What does __new__ do?

__new__

is a static method called on the class itself. It must return an instance of the class and is responsible for the actual memory allocation.

Where does __new__ come from?

Everything in Python is an object, and the base

object

class defines

__new__

. Even a class that does not explicitly inherit anything is still an

object

subclass.

<code>my_instance = SimpleObject(name="bob")
print(isinstance(my_instance, object))  # True
print(isinstance(42, object))         # True
print(isinstance('hello', object))     # True
print(isinstance({"my": "dict"}, object))  # True
</code>

Difference between __new__ and __init__

__new__

creates the object (allocates memory and returns it). Once the object exists,

__init__

runs to initialize its attributes.

Python's object creation process

When a new object is created, Python executes the following functions in order:

__new__

: allocate memory and return a new object

__init__

: initialize the newly created object

Overriding

__new__

lets you intervene in this process:

<code>class SimpleObject:
    greet_name: str
    def __new__(cls, *args, **kwargs):
        print("__new__ method")
        return super().__new__(cls)
    def __init__(self, name: str):
        print("__init__ method")
        self.greet_name = name
    def say_hello(self) -> None:
        print(f"Hello {self.greet_name}!")

my_instance = SimpleObject(name="bob")
my_instance.say_hello()
</code>

Running this code prints:

<code>__new__ method
__init__ method
Hello bob!
</code>

The output confirms that

__new__

runs before

__init__

. The default

__new__

simply returns

super().__new__(cls)

:

<code>class SimpleObject:
    def __new__(cls, *args, **kwargs):
        return super().__new__(cls)
</code>

Practical Application 1: Inheriting immutable types

By overriding

__new__

, you can inherit from immutable built‑ins like

tuple

. The following

Point

class stores coordinates in a tuple while adding custom methods and validation:

<code>class Point(tuple):
    x: float
    y: float
    def __new__(cls, x: float, y: float):
        if x < 0 or y < 0:
            raise ValueError("x and y must be positive")
        return super().__new__(cls, (x, y))
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y
    def distance_from(self, other_point: 'Point'):
        import math
        return math.sqrt((other_point.x - self.x) ** 2 + (other_point.y - self.y) ** 2)

p = Point(1, 2)
p2 = Point(3, 1)
print(p.distance_from(p2))  # 2.23606797749979
</code>

Practical Application 2: Adding metadata

You can derive from

float

and attach extra information, such as a currency symbol:

<code>class Currency(float):
    def __new__(cls, value: float, symbol: str):
        obj = super(Currency, cls).__new__(cls, value)
        obj.symbol = symbol
        return obj
    def __str__(self) -> str:
        return f"{self.symbol}{self:.2f}"

price = Currency(12.768544, symbol='€')
print(price)  # €12.77
print(isinstance(price, float))  # True
print(f"{price.symbol}{price * 2}")  # €25.54
</code>

Practical Application 3: Singleton pattern

Using

__new__

you can ensure a class has only one instance:

<code>class Singleton:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

singleton1 = Singleton()
singleton2 = Singleton()
print(singleton1 is singleton2)  # True
</code>

Other practical uses

Control instance creation : Add validation, logging, or modification before an object is created.

Factory methods : Decide which subclass to instantiate inside

__new__

.

Caching : Return a previously created object instead of allocating a new one.

Conclusion

This article explored Python’s object creation pipeline, clarified the distinct responsibilities of

__new__

and

__init__

, and demonstrated how overriding

__new__

enables advanced patterns such as immutable inheritance, metadata enrichment, and singletons, leading to more efficient and expressive code.

design patternsPythonobject-orientedimmutability__init____new__
Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.