Modules -> OOP -> Dunder methods -> The init method

The init method


Related code
class Dog:
    """Represents a dog.

    attributes: name: str, age: int, energy: float
    """

    def set_info(self, name, age, energy):
        self.name = name
        self.age = age
        self.energy = energy

    def print(self):
        print(f'Name: {self.name}, Age: {self.age}')

murki = Dog()
murki.set_info("Murki", 2, 50)

Python offers special methods that are triggered during specific actions. One of the most important is __init__ (two underscore characters, followed by init, and then two more underscores), which runs automatically when an object is created (Dog()).

class Dog:
    def __init__(self):
        print("Test")

Just like any other method, special methods, like __init__, have self as its first parameter, which refers to the object being created. In the above __init__ method, we printed "Test", and we expecte this string to be printed once we initialize a Dog instance.

>>> murki = Dog()
Test

In the above example we created a dog (Dog()), and we saved it to a variable (murki). After the execution of this line we see the string Test printed. That means when Dog() was executed, Python activated Dog's __init__ method, which printed Test

init is not used to print anything. We printed Test just to prove that this method is activated in object creating. This method is typically used to set object attributes and assign values to them.

An object is characterized by its attributes. It is the attributes that differentiate objects of different classes, not the class name itself. And creating an object without setting some attributes isn't much useful. So, most of the time, everytime we create an object (murki = Dog()) we immediately set its attributes after its initialization (murki.set_info("Murki", 2, 50)).

This is where __init__ becomes useful, it allows us to assign attributes and their values at the moment the instance is created.

class Dog:
    def __init__(self):
        self.name = "Murki"
        self.age = 3
        self.energy = 50

murki = Dog()
print(murki.name, murki.age, murki.energy)
Murki 3 50
>>>

Outside the class, we refer to the dog using the variable name it's assigned to, in this case, murki. Inside the class, however, we refer to the object as self. So, while outside the class we might set an attribute like this: murki.name = "Murki", inside the class we do the same using self: self.name = "Murki".

That's exactly what we did when we updated the __init__ method in the Dog class, we set three attributes, name, age, and energy, and assigned them the values "Murki", 3, and 50, respectively.

Now, everytime we create a dog, they will automatically have name, age and energy attributes:

>>> murki = Dog()
>>> murki.name, murki.age, murki.energy
('Murki', 3, 50)
>>> llesi = Dog()
>>> llesi.name, llesi.age, llesi.energy
('Murki', 3, 50)
>>>

But as we see, these attributes will have the same values too, which doesn't make much sense, creating multiple dogs with the same name, age and energy level.

Just like any other method, __init__ can have extra parameters, beside self, and their arguments are passed on instance creation.

class Dog:
    def __init__(self, a, b, c):
        self.name = a
        self.age = b
        self.energy = c
>>> murki = Dog("Murki", 3, 50)
>>> murki.name, murki.age, murki.energy
('Murki', 3, 50)
  • "Murki" is assigned to parameter a, and then its value is assigned to attribute name
  • 3 is assigned to parameter b, and then its value is assigned to attribute age
  • 50 is assigned to parameter c, and then its value is assigned to attribute energy

Now we can create multiple dogs with same attributes, different values

>>> murki = Dog("Murki", 3, 50)
>>> murki.name, murki.age, murki.energy
('Murki', 3, 50)
>>> llesi = Dog("Llesi", 1, 70)
>>> llesi.name, llesi.age, llesi.energy
('Llesi', 1, 70)
>>>

Most of the time it is a good idea to name parameters the same as attributes; logically they represent the same thing:

class Dog:
    def __init__(self, name, age, energy):
        self.name = name
        self.age = age
        self.energy = energy

Previously we used set_info to set dog attributes and their values, now we use __init__. We see that these two methods have the same parameters and the same body implementation. The only difference is their names. So from now on we won't need set_info method since its functionality is replaced by__init__. Here is the final version of our class:

class Dog:
    """Represents a dog.

    attributes: name: str, age: int, energy: float
    """

    def __init__(self, name, age, energy):
        self.name = name
        self.age = age
        self.energy = energy

    def print(self):
        print(f'Name: {self.name}, Age: {self.age}')
>>> murki = Dog("Murki", 2, 50)
>>> murki.print()
Name: Murki, Age: 2