15 May 2021
What could be better than a Python blog with the first content on __init__
method?
I couldn't find. What about you? Could you? What about __init__
? Is it really a
constructor?
When I first learned OOP concepts in Python with Java and C++ background,
I misconceptionally thought that __init__
is a weird
name for constructor in Python because other languages choose
pretty appropriate name for it to make object creation easy: a method
having same name as the class itself. Let's remember what constructor is
in these 'other languages'. In Java and C++, constructor allocates space
for a new instance.
A simple Java Constructor example:
class Blog{
private String content;
public Blog(String content) {
this.content = content;
}
}
class Blog{
private:
string content;
public:
Blog(string content){
this->content = content;
}
};
This how we define a simple class and its instance in Python:
class Blog:
def __init__(self, content):
self.content = content
my_blog = Blog("content")
Python has an additional self
parameter inside the class.
In Python, self
refers to the instance of the object
itself. If it is not a @staticmethod
or a @classmethod
,
we use self
as a first parameter. In the case of not using it, you will get TypeError
.
On the other hand, Java and C++ passes objects hiddenly to the methods so it is the reason why we don't see this
keyword explicitly.
Let's get back to Python. It is not obligatory to name it self but it is conventionally
preffered by developers to identify it easily. Denotation can be change
but its place cannot! It is always the first parameter. After adding a new method to our class,
it becomes:
Blog
class and slightly change our code.
class Blog:
def __init__(self, content: str) -> str:
self.content = content
def add_new_content(self, new_content: str) -> str:
self.content = self.content + new_content
Now we are able to add a new content to our current content. I will create an instance and test it by printing content attribute.
my_blog = Blog("content")
print(my_blog.content)
# content
my_blog.add_new_content(" and new content")
print(my_blog.content)
# content and new content
No surprise to print that out. But a surprising thing comes up with printing types.
print(type(my_blog.add_new_content))
# <class 'method'>
print(type(Blog.add_new_content))
# <class 'function'>
First parameter of init is self
so the instance of the class has been already created and is represented
by self.
In fact, init is immediately called after the instance is
created. Object creation is role of constructor. But, in Python, who
creates objects? In other words, what can we use if we want to modify
constructor? Of course __new__
method is the right answer
of this question.
class Blog:
def __new__(cls, *args, **kwargs):
obj = super(Blog, cls).__new__(cls)
return obj
Here, the first argument is cls
, it is similar to
self
but cls
is
used to represent that a class itself is passed as the argument. Apart from certain dunder methods, cls
is
the first argumenet of class methods that are decorated by @classmethod
. So,
__new__
takes class itself, allocate memory for new object and finally returns it. That is
exactly what constructor should do. Right? By the way, did I say __init__
does not return anyhing?
Let's back to our minimal class example to get __init__
returned something.
class Blog:
def __init__(self, content):
self.content = content
return self.content
my_blog = Blog("content")
# TypeError: __init__() should return None, not 'str'
Did you see that? Python has already known this rule? __init__
does not return anything.
In short, __new__
controls
instance creation and returns new instance. On the other hand,
__init__()
is responsible for initialization of instance
and is not expected to return anything.
Let's try it out with an example:
import inspect
class Blog:
def __new__(cls, *args, **kwargs):
print("Hi from {}".format(inspect.stack()[0][3]))
obj = super(Blog, cls).__new__(cls)
return obj
def __init__(self, content):
print("Hi from {}".format(inspect.stack()[0][3]))
self.content = content
and create an object
my_blog = Blog("<h2> Content </h2>")
It will be written in console:
Hi from __new__
Hi from __init__
Here it is obvious that when a new instance is created,
__new__
is called first then __init__
is used.
Keep in mind that if __new__
fails, __init__
also fails
because object creation will be not done successfully.
class Blog:
def __new__(cls, *args, **kwargs):
print("I didn't return an object")
pass
def __init__(self, content):
print("I didn't initialize an onject")
self.content = content
my_blog = Blog("content")
only prints out:
I didn't return an object
and nothing happens on the side of __init__
.
And when the content attribute is tried to get by
my_blog = Blog("content")
my_blog.content
AttributeError
raises:
AttributeError: 'NoneType' object has no attribute 'content'
Let's summarize what we have learnt:
__init__
does is that initialization of
instance variables right after object creation.