Python Constructor and Initialisation
Most object-oriented programming languages have the concept of a constructor, a method that creates and initialises the object when it is created.
For Python, the constructor is split into __init__
(for initialisation)and __new__
(for creation).
Let’s look at how the other languages deal with constructor
first.
JavaScript
In JavaScript, the constructor
method is a syntax sugar method of a class
for creating and initialising an object of that class. And class
is another syntax sugar for a type of function
.
class Polygon {
constructor() {
this.name = ‘Polygon’;
}
}const poly1 = new Polygon();console.log(poly1.name);
// expected output: “Polygon”
When the code new Polygon(...)
is executed, the following things happen:
- It first creates an empty new object
{}
- It creates
__proto__
onpoly1
and makes it point toPolygon.prototype
sopoly1.__proto__ === Polygon.prototype
- It executes
poly1.prototype.constructor
(which is definition of functionPolygon
) with the newly created empty object as its context (this
), so thename
property gets added to newly created object. - It returns newly created object
Python __new__ vs. __init__
object.__new__
(cls[, ...])Called to create a new instance of class cls.
__new__()
is a static method that takes the class of which an instance was requested as its first argument. The remaining arguments are those passed to the object constructor expression . The return value of__new__()
should be the new object instance (usually an instance of cls).If
__new__()
is invoked during object construction and it returns an instance or subclass of cls, then the new instance’s__init__()
method will be invoked like__init__(self[, ...])
, where self is the new instance and the remaining arguments are the same as were passed to the object constructor.
object.__init__
(self[, ...])Called after the instance has been created by
__new__()
, but before it is returned to the caller.Because
__new__()
and__init__()
work together in constructing objects (__new__()
to create it, and__init__()
to customize it), no non-None
value may be returned by__init__()
; doing so will cause aTypeError
to be raised at runtime.
So per the documentation says, __new__
is for the creation of a new instance while __init__
is for the initialisation of a new instance returned from __new__
.
Most time in Python, we won’t need to touch __new__
when e.g. we define a custom class, but we can define a custom __init__
. So how can our custom __init__
call the default __new__?
class Polygon():
def __init__(self, area):
self.area = areapoly1 = Polygon(20)
That’s because the `Polygon.__new__
is able to access the default object.__new__
through the MRO order. So calling Polygon.__new__
is the same as calling object.__new__
if no other parent class is defined.
- Once the
Polygon.__new__
(aka.object.__new__)
is found, it passesobject.__new__(Polygon, *args, **kwargs)
aka.Polygon.__new__(Polygon, area=20)
- An instance of
Polygon
is created akapoly1
and the__init__
method is invoked on the instancepoly1
object.__new__(self, *args, **kwargs)
aka.object.__new__(poly1, area=20)
What if it involves class inheritance? The result is very similar:
class Polygon():
def __init__(self, area):
self.area = areaclass Square(Polygon):
def __init__(self, area):
super().__init__(area)square1 = Square(10)print(Square.__mro__)
print(Square.__new__ is Polygon.__new__)
print(Square.__new__ is object.__new__)
====================>
(<class '__main__.Square'>, <class '__main__.Polygon'>, <class 'object'>)
True
True
So most of time we don’t need to touch __new__
. But sometimes we can use __new__()
to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation. It is also commonly overridden in custom metaclasses in order to customise class creation.
Below is an example just to showcase you can bend the rule.
class Polygon:
def __new__(cls):
print (f"construct {cls}")
return object.__new__(cls)poly1 = Polygon()===============>
construct <class '__main__.Polygon'>
The following is a more common example for subclass immutable object:
class ModularTuple(tuple):
def __new__(cls, tup, times=3):
tup = (int(x) * times for x in tup)
return super(ModularTuple, cls).__new__(cls, tup)mt = ModularTuple((1,2,3))print(mt)
print(tuple.__new__ is ModularTuple.__new__)======================>
(3, 6, 9)
True
I hope you get a basic understanding of __init__
and __new__ i
n Python and how the two interacts with each other.
Happy Reading!