Init vs New#
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.
When
Singleton()
is called for the first time,cls._instance
isNone
.A new instance is created and assigned to
cls._instance
.For all subsequent calls to
Singleton()
,cls._instance
is no longerNone
, 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