Understanding __init__ and __str__ methods
The __init__()
method
The __init__()
method makes it easier to instantiate an object.
Let’s understand it through a simple example.
class Time:
"""Represents time of the day"""
def __init__(self, hour = 0, minute = 0, second = 0):
self.hour = hour
self.minute = minute
self.second = second
def __str__(self):
return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)
The code above defines Time
- a class for representing time of the day.
The attributes of the class are hour
, minute
, and second
. By definition,
they are also the attributes of the instance self
.
The class also has two methods defined, nameley, __init__()
and __str__()
.
The __init__()
method has four parameters:
self
- an instance of the classTime
hour
- an integerminute
- an integersecond
- an integer.
It is tempting to conclude that the class attributes are passed as parameters
to the __init__()
method. However, the fact is that, the parameters of
__init__()
method are delibertely given the same names as the attributes.
Also, the parameters of the __init__()
method are initialised to zero.
In the function body, the class attributes are assigned the values of the
method parameters. As a result, the attributes are also initialised to zero.
When instantiating a new object, the attributes take the values assigned to
method parameters. With __init__()
method in place, objects can be
instantiated from a single command.
t1 = Time(1, 2, 3)
t2 = Time1(4, 5, 6)
Technically, it is not necessary to have same names for class attributes and method parameters. However, they are given common names for the sake of convenience.
For example, in the code snippet shown below, the class atrributes and method parameters are given different names. While the code is logically correct, the choice of variable names makes it less intuitive to understand and hence more difficult to maintain in the long term.
class Time1:
"""Represents time of the day"""
def __init__(self, car = 0, bus = 0, cycle = 0):
self.hour = car
self.minute = bus
self.second = cycle
What’s in a name?
Now comes the interesting part. We know that the function name doesn’t influence what a function does. It is like any other variable name - just a label to identify and use a particular piece of code.
Things appear to become a bit different when you are dealing with methods
like __init__()
and __str__()
. For example, if you change the name from
__init__()
to foo()
, without changing the body, the functionality provided
by __init__()
is lost. This is interesting because we have changed only the
function name but not the body.
class Time2:
"""Represents time of the day"""
def foo(self, hour = 0, minute = 0, second = 0):
self.hour = hour
self.minute = minute
self.second = second
t3 = Time2(10, 23, 45)
Output:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-22-8184df36575a> in <module>()
----> 1 t3 = Time2(10, 23, 45)
2 print(t3)
TypeError: object() takes no parameters
How can this happen? What’s going on?
Let’s dig a bit deeper to understand to understand this better.
When we create an instance, the following steps are carried out under the hood.
- The interpreter invokes the method
__call__()
. __call__()
method invokes__new__()
method, which creates a new instance.__call__()
method then calls__init__()
method which initialises the instance created by__new__()
.
__init__()
is a predefined function in Python. But, there is also a provision
to define it the way we want it to be and make it part of the class definition.
Our definition, obviously, will be a new method and it doesn’t change the
original function.
While invoking __init__()
method, the __call__()
method first looks at the
class definition. If __init__()
is available there itself, then that version
is invoked. If not, the pre-defined version is invoked.
When you change the name from __init__()
to foo()
in the class definition,
__call__()
concludes that __init__()
is not defined in the class and uses
the pre-defined version of __init__()
. Obviously, this is not going to give
you the functionality that you implemented in the method foo()
. Figure 1
shows a flowchart for choosing the __init__()
method.
Of course, it is possible to instantiate an object without including the
__init__()
method in the class definition. But, this takes more effort.
t3 = Time2()
t3.hour = 10
t3.minute = 23
t3.second = 45
If you observe closely, our __init__()
method is just a function-form of this
syntax!
In summary,
__init__()
method enables you to instantiate objects with a single command.- It provides a function-like syntax for instantiating objects and setting their attribute values.
The __str__()
method
__str__()
function makes it easier to display objects using print()
function. If __str__()
is not included in the class definition, print()
displays some meta data concerning the object.
As an example, let us print the t2
- an instance of the class Time1
. I am
particulalrly considering Time1
because the class definition doesn’t include
__str__()
method.
print(t2)
Output:
<__main__.Time1 object at 0x7f09a8496d30>
There is no error message, but we are not getting what wanted to see.
However, printing an instance of Time
gives no surprises because it has a
__str__()
method defined.
print(t1)
Output:
01:02:03
So, what’s happening here?
Like __init__()
method, __str__()
is also a pre-defined function. As in the
case of __init__()
, you can define your own version of __str__()
, and
include it in the class definition.
When you try to print an object, the print()
function calls __str__()
method and prints the value that is returned by it. If the class definition
contains a __str__()
method, then the value returned by that function is
printed. Otherwise, the value returned by the pre-defined __str__()
is
printed. Figure 2 shows a flowchart for choosing the __str__()
method.
Scroll up and look at the body of __str__()
method in the definition of Time
class. It converts the attribute values from integers to string, and returns
one string value in a particular format. When called, the print()
function
simply displays the formatted string value.
Conclusion
-
__init__()
methods makes it easy to instantiate objects.__str__()
makes it easy to print objects. -
Including
__init__()
and__str__()
methods in the class definition is useful but not necessary. -
Both of them are pre-defined functions. But they can also be defined by users to be included in their classes.
-
The scope of user-defined
__init__()
and__str__()
methods is limited to the class in which they are defined. -
When dealing with
__init__()
and__str__()
, the method names are as important as the method body. This is because there are other methods that are programmed to look for functions with names__init__()
and__str__()
. -
__call__()
method invokes the__init__()
to initialise an object with a given set of attribute values. Similarly,print()
calls__str__()
method to display the string representation of the object. -
In both the cases, the pre-defined versions are called only when the user-defined versions are not available.
References
- Allen Downey. Think Python. How to Think Like a Computer Scientist. Green Tea Press, Needham, Massachusetts
- John Sturtz. Python Metaclasses. https://realpython.com/python-metaclasses/ Accessed on 2020-02-17.