The Initial Post: The truth about __init__


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?


Python Logo

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:

  1. Constructor is use its own class and produce an instance
  2. init takes an instance and may not return anything.
  3. Rule 1 and Rule 2 show that init is not a constructor
  4. What __init__ does is that initialization of instance variables right after object creation.
  5. init is immediately called after object creation
  6. new is responsible for object creation
  7. So in Python new is consturctor, not init

Tags: #python