Constructors & __init__

Python actually splits object construction into two steps - __new__ creates the object, __init__ initializes it - and knowing the difference clears up a lot of confusion about what "the constructor" really is.

In everyday Python, __init__ is the method people call "the constructor," and for the vast majority of classes that's close enough. But __init__ doesn't actually create the object - it initializes an object that already exists. The true object-creation step is __new__, which __init__ follows.

__init__: Initialization, Not Creation

__init__ is called automatically after the object has already been created, with the new (already-existing) instance as self. Its job is to set up instance state - it must not return anything other than None.

By the time __init__ runs, p already exists as a Point instance; __init__ just fills in x and y.

Returning a non-None value from __init__ raises TypeError - it's a hard rule enforced by Python, reflecting that __init__'s only job is to mutate self, not to produce a value.

class Point:
    def __init__(self, x: float, y: float) -> None:
        self.x = x
        self.y = y

p = Point(3, 4)
print(p.x, p.y)   # 3 4

The __new__ Distinction

__new__ is a static method (implicitly, even without the decorator - it's special-cased) responsible for actually allocating and returning a new instance. It receives the class (cls) as its first argument, not an instance, because the instance doesn't exist yet. object.__new__ provides the default implementation almost every class relies on implicitly.

__new__ runs first and returns the instance; that instance is then passed automatically as self to __init__.

class Traced:
    def __new__(cls, *args, **kwargs):
        print(f'__new__ called with cls={cls.__name__}')
        instance = super().__new__(cls)
        return instance

    def __init__(self, value):
        print(f'__init__ called with self={self!r}')
        self.value = value

t = Traced(42)
# __new__ called with cls=Traced
# __init__ called with self=<...Traced object ...>

Constructor Terminology

Strictly, "the constructor" is the combination of __new__ + __init__ working together (with __new__ doing the actual construction). In casual Python usage - and in most day-to-day code - people say "constructor" to mean __init__, since that's the method almost everyone overrides. This article uses "constructor" loosely to mean __init__ except where __new__ is explicitly discussed.

Object Creation Flow: Calling a Class

When you write Point(3, 4), Python's type.__call__ (the mechanism behind calling a class) does roughly this: call Point.__new__(Point, 3, 4) to get a new object; if the returned object is an instance of Point (or a subclass), call Point.__init__(obj, 3, 4) on it; then return the object.

Python calls __new__(cls, ...) to allocate/produce the instance.

If the result is an instance of cls (or a subclass), __init__(self, ...) is called on it automatically.

If __new__ returns something that is NOT an instance of cls, __init__ is skipped entirely.

The fully constructed object is returned from the call expression.

# Conceptually, Point(3, 4) is roughly:
obj = Point.__new__(Point, 3, 4)
if isinstance(obj, Point):
    Point.__init__(obj, 3, 4)
result = obj

When to Override __new__

Almost all everyday classes only need to override __init__. Overriding __new__ is reserved for specialized cases: subclassing an immutable built-in type (like int or str, whose value must be fixed at creation time, before __init__ could set it), implementing the singleton pattern, or controlling which subclass gets instantiated.

int is immutable, so its underlying value must be fixed inside __new__ - by the time __init__ would run, it's too late to change the int's value.

class PositiveInt(int):
    def __new__(cls, value):
        if value <= 0:
            raise ValueError('value must be positive')
        return super().__new__(cls, value)   # int's value must be set at creation time

p = PositiveInt(5)
print(p, type(p))   # 5 <class '__main__.PositiveInt'>
PositiveInt(-1)       # raises ValueError

Default __init__ and Inheritance

If a class defines no __init__, Python uses the inherited one (ultimately object.__init__, which does nothing). When a subclass defines its own __init__, it does NOT automatically call the parent's - you must call super().__init__(...) explicitly if you want the parent's initialization to run too.

class Animal:
    def __init__(self, name: str) -> None:
        self.name = name

class Dog(Animal):
    def __init__(self, name: str, breed: str) -> None:
        super().__init__(name)   # explicitly run Animal's init
        self.breed = breed

d = Dog('Rex', 'Labrador')
print(d.name, d.breed)   # Rex Labrador

Multiple Instances, Independent State

Each call to a class creates a distinct object with its own __init__ run, so instance attributes never leak between instances (unless you deliberately share mutable state via a class attribute - see Classes & Objects).

p1 = Point(0, 0)
p2 = Point(1, 1)
p1.x = 99
print(p1.x, p2.x)   # 99 1 - independent

Practical Usage: Validation in __init__

A very common pattern is validating constructor arguments in __init__ and raising an exception immediately if they're invalid, so an object is never left in a half-valid state.

class Temperature:
    def __init__(self, celsius: float) -> None:
        if celsius < -273.15:
            raise ValueError('temperature below absolute zero')
        self.celsius = celsius

Temperature(-300)   # raises ValueError before any instance is usable

Edge Cases

If __new__ returns an instance of a different, unrelated class, __init__ is silently skipped for that returned object - a subtle trap when overriding __new__ for factory-like behavior.

class A:
    def __new__(cls):
        return object()   # not an instance of A

    def __init__(self):
        print('init called')   # never runs

a = A()
print(type(a))   # <class 'object'> - not A at all, and __init__ never printed

Key Takeaways

__init__ initializes an object that already exists; it must return None.

__new__ is the actual object-creation step, receiving cls and returning the new instance; it runs before __init__.

Calling a class runs __new__ then, if the result is an instance of that class, __init__ automatically.

Override __new__ only for special cases: immutable-type subclassing, singletons, or controlling instantiation - override __init__ for ordinary initialization.

A subclass's __init__ does not automatically call its parent's __init__ - use super().__init__(...) explicitly.

class Point: def __init__(self, x, y): self.x = x self.y = y return self # TypeError: __init__ should return None class Point: def __init__(self, x, y): self.x = x self.y = y # implicitly returns None

__init__ must return None (or nothing); returning any other value raises TypeError, since __init__'s job is only to mutate self.

class Dog(Animal): def __init__(self, name, breed): self.breed = breed # Animal.__init__ never runs - self.name is never set class Dog(Animal): def __init__(self, name, breed): super().__init__(name) self.breed = breed

Defining __init__ in a subclass overrides the parent's entirely; call super().__init__(...) explicitly to run the parent's initialization too.

class Config(int): def __init__(self, value): self.value = value # too late - int's value was already fixed by __new__ class Config(int): def __new__(cls, value): return super().__new__(cls, value)

For immutable built-in types like int, str, and tuple, the value must be set in __new__, since by the time __init__ runs the immutable value is already fixed.

What is the difference between __new__ and __init__?

__new__ actually creates and returns the new object (receiving the class, cls, as its first argument); __init__ initializes an already-created object (receiving the instance, self) and must return None.

Why must __init__ return None?

Because __init__'s sole responsibility is to mutate the already-created instance in place; Python enforces this by raising TypeError if __init__ returns anything other than None.

In what order do __new__ and __init__ run when you call MyClass(...)?

__new__ runs first to create the instance; then, if the object __new__ returned is an instance of MyClass (or a subclass), __init__ is called automatically on it.

When would you need to override __new__ instead of just __init__?

When subclassing an immutable built-in type (whose value must be fixed at creation time), implementing a singleton pattern, or controlling which class/subclass actually gets instantiated - ordinary classes almost never need this.

Does a subclass's __init__ automatically call its parent class's __init__?

No - defining __init__ in a subclass completely overrides the parent's; you must explicitly call super().__init__(...) to also run the parent's initialization logic.

What happens to __init__ if __new__ returns an object that is not an instance of the class being constructed?

__init__ is skipped entirely for that object - Python only calls __init__ automatically when __new__'s return value is an instance of the class (or a subclass) being constructed.

Interview: why can't you validate/fix an int subclass's value inside __init__?

Because int is immutable - its underlying value is fixed at creation time inside __new__ (via object allocation), so by the time __init__ runs on the already-created instance, the value can no longer be changed; validation or value-setting for immutable types must happen in __new__.

Write a class Singleton that ensures only one instance is ever created, using __new__.

class Singleton: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance a = Singleton() b = Singleton() print(a is b) # True

__new__ checks a class attribute holding the single allowed instance; if it already exists, it's returned instead of creating a new one, so every call to Singleton() yields the same object.

What does this print?

Derived init Derived.__init__ overrides Base.__init__ completely and does not call super().__init__(), so Base's initialization code never runs - only 'Derived init' prints.

Write a class ImmutablePoint subclassing tuple, overriding __new__ to store (x, y), with a __repr__ for readability.

class ImmutablePoint(tuple): def __new__(cls, x: float, y: float): return super().__new__(cls, (x, y)) def __repr__(self) -> str: return f'ImmutablePoint({self[0]}, {self[1]})' p = ImmutablePoint(1, 2) print(p) # ImmutablePoint(1, 2) print(tuple(p)) # (1, 2)

Since tuple is immutable, its contents must be supplied at creation time via __new__ rather than set afterward in __init__.

class Base:
    def __init__(self):
        print('Base init')

class Derived(Base):
    def __init__(self):
        print('Derived init')

Derived()