Init vs New#

Twitter Handle LinkedIn Profile GitHub Profile Tag

Demo Example#

from typing import Type
from typing_extensions import Self

from rich.pretty import pprint

class Base:
    def __new__(cls: Type[Self]) -> Self:
        print(f"__new__ called | {cls=} | {type(cls)=}")
        return object.__new__(cls)

    def __init__(self: Self) -> None:
        print(f"__init__ called | {self=} | {type(self)=}")
        self.a = 1

base = Base()
__new__ called | cls=<class '__main__.Base'> | type(cls)=<class 'type'>
__init__ called | self=<__main__.Base object at 0x107dfab80> | type(self)=<class '__main__.Base'>

Call Stack#

Below we present a rough call stack of the above code.

Base() 
   |
   |---> type_call (type.__call__) [Objects/typeobject.c]
            |
            |---> type_new (__new__) 
                    |
                    |---> Base.__new__(Base, ...)
                            |
                            |---> __new__ (Python method)
                            |           |
                            |           |---> print("__new__ called | cls=<class '__main__.Base'> | type(cls)=<class 'type'>")
                            |           |---> object.__new__(cls)
                            |
                            |---> object_new (object.__new__) [Objects/object.c]
                                    |
                                    |---> Allocate Base instance
                                    |---> Return new instance
            |
            |---> type_init (__init__)
                    |
                    |---> Base.__init__(base)
                            |
                            |---> __init__ (Python method)
                                    |---> print("__init__ called | self=<__main__.Base object at 0x...> | type(self)=<class '__main__.Base'>")
                                    |---> self.a = 1
   |
   |---> Return initialized instance 'base'

Constructor#

__new__ is often referred to as the constructor because it constructs (creates) a new instance of the class. Without going into the details of the C code, we can see that __new__ is called first, followed by __init__.

The signature of __new__ is __new__(cls: Type[Self]) -> Self, where cls is the class itself. Note that cls is of type Type[Self], which means this is not an instance of the class, but a type object. It returns an instance of the class Self.

Here we manually call __new__ to “mimic” the internal call.

base = Base.__new__(Base)

assert isinstance(base, Base)
__new__ called | cls=<class '__main__.Base'> | type(cls)=<class 'type'>

Initializer#

__init__ is referred to as the initializer because it initializes the newly created instance and sets its attributes.

The signature of __init__ is __init__(self: Self) -> None, where self is the instance itself.

We manually call __init__ to “mimic” the internal call.

Base.__init__(base)

assert base.a == 1
__init__ called | self=<__main__.Base object at 0x10c214040> | type(self)=<class '__main__.Base'>

Singleton#

Probably the most common use case of __new__ is to implement the singleton pattern. We see a implementation below, and will discuss it in detail in design patterns.

class Singleton:
    _instance: Type["Singleton"] = None

    def __new__(cls: Type[Self]) -> Self:
        if cls._instance is None:
            print(f"Creating new instance of {cls.__name__}")
            cls._instance = super().__new__(cls)
        else:
            print(f"Using existing instance of {cls.__name__}")
        return cls._instance

    def __init__(self):
        print(f"Initializing Singleton instance | self={self} | type(self)={type(self)}")
        self.value = 42


s1 = Singleton()
s2 = Singleton()

print(s1 is s2)
print(id(s1), id(s2))
Creating new instance of Singleton
Initializing Singleton instance | self=<__main__.Singleton object at 0x10c214310> | type(self)=<class '__main__.Singleton'>
Using existing instance of Singleton
Initializing Singleton instance | self=<__main__.Singleton object at 0x10c214310> | type(self)=<class '__main__.Singleton'>
True
4498473744 4498473744

One thing one needs to understand is that class attributes are shared across all instances of the class. Hence, when we change the class attribute, it affects all instances. You can think of it as a global variable.

  1. When Singleton() is called for the first time, cls._instance is None.

  2. A new instance is created and assigned to cls._instance.

  3. For all subsequent calls to Singleton(), cls._instance is no longer None, so the existing instance is returned.

Because _instance is a class attribute, it’s shared across all calls to the class constructor. This allows the class to “remember” if an instance has already been created.

class Demo:
    _instance = 1

    def __init__(self) -> None:
        self.a = "hi"

d1 = Demo()
pprint(d1._instance)

Demo._instance = 2

d2 = Demo()
pprint(d2._instance)
1
2

References And Further Readings#