Understanding special methods in Python
↳ Python
21 Jul 2020 | 17 minute read
Table of Contents
When diving into the world of Python, you might have encountered methods such as the __init__
, __str__
or __getitem__
methods. These are special methods that are called by the Python interpreter and not by you, and this article will showcase how they are used and the power of them.
As there are numerous special methods across the Python programing language, this article won't explore all of them. Instead, we'll focus on a few, and as your understanding deepens on how they are used you'll be able to extrapolate how others are used.
By understanding how special methods work, you're on your way to understanding the Python data model in more detail, and what makes Python code pythonic. Ultimately, it will enable you to become a more experienced Python developer.
Special, magic and dunder methods
Special methods are known by many names, the most common ones being magic methods
and dunder methods
. These are just different names for the same thing, and nothing you need to worry about.
Why use special methods?
Special methods are called by the Python interpreter and not by the user. As of this, special methods creates somewhat of a contract between Python developers, as there is a unified way to reach the same result.
As of this, you can avoid counter-intuitive implementations and write code in a more standardized way. This enables you to write code not only more pythonic, but code that is easier to understand and grasp for other Python developers. Ultimately, by defining special methods in your classes, your classes will start to resemble built-in types.
Furthermore, some functions in the Python standard library might expect you to implement one or more special methods to use the function on that object.
Your first special methods
When exploring special methods and how they are used, we will build on an example throughout the entire article. In our example, we are to simulate student attendance for a class, where the students have been hard-coded in the private variable _students
with their names sorted in alphabetical order.
By building upon a real example, and not have separate examples for each special method, I hope that you will get a better understanding of how to utilise special methods in your classes.
What am I? (__init__)
When creating an instance of the ClassAttendants
class, we want to initialise the object with the _students
. To do so, we utilise the __init__
special method, which is the constructor for a class in Python.
The __init__
special method will run when an object is created (attendants = ClassAttendants()
), and is typically where variables are defined on the object.
As you can see in the snippet below, we assign the _students
variable to the object itself, where self
represents the instance of the object itself.
class ClassAttendants:
def __init__(self):
self._students = sorted(
[
"Odin",
"Frigg",
"Balder",
"Loki",
"Thor",
"Freya",
"Frey",
"Heimdall",
"Hel",
"Vidar",
"Vale",
"Harbard",
"Kvasir",
"Njord",
]
)
Having defined our __init__
special method, the _students
variable will be defined on our objects when they are created.
How many students attended the class? (__len__)
To evaluate how many students attended the class, we need to know the length of the _students
list. To do so, we could do the following:
attendants = ClassAttendants()
len(attendants._students)
Which would give the following output:
14
As you can see, this approach works fine and produces the desired result. However, it as not encouraged as _students
is a private variable. Furthermore, there are numerous benefits to using special methods as mentioned earlier in this article.
A more Pythonic approach would be to implement the __len__
method on the ClassAttendants
class. By doing so the implementation details of the ClassAttendants
class, such as the variable naming (_students
), would be hidden for any developer using the class. This is desirable, as it enables any developer to use your class without knowing the exact implementation of it.
Your code would also subscribe to the common practice of using the len
function to get the length of the object you're working with.
class ClassAttendants:
def __init__(self):
self._students = sorted(
[
"Odin",
"Frigg",
"Balder",
"Loki",
"Thor",
"Freya",
"Frey",
"Heimdall",
"Hel",
"Vidar",
"Vale",
"Harbard",
"Kvasir",
"Njord",
]
)
def __len__(self):
return len(self._students)
To execute the __len__
method, you shouldn't call it directly. As mentioned earlier, special methods are called by the Python interpreter and not by the developer.
Instead, you should create a ClassAttendants
instance, and call the len
function with the ClassAttendants
object as the argument. See the snippet below.
attendants = ClassAttendants()
len(attendants)
By doing so, the interpreter will deduct that you want to execute the __len__
method and do so for you. The snippet above produces the following output:
14
Which students attended the class? (__str__)
When calling print
on a ClassAttendants
object, we want to display the students attending the class in a format that is readable for the (human) caller of the function.
What happens if we try to print
a ClassAttendants
objects?
attendants = ClassAttendants()
print(attendants)
<classroom.ClassAttendants object at 0x7f7802d38860>
As you can see, this isn't quite readable for humans but gives us some context on what kind of object it is. It would be desirable to know the names of the attendants.
To display a string readable for humans, we need to implement the special method __str__
on ClassAttendants
. The __str__
method is called when a string readable representation is requested, such as when the print
function is called.
class ClassAttendants:
def __init__(self):
self._students = sorted(
[
"Odin",
"Frigg",
"Balder",
"Loki",
"Thor",
"Freya",
"Frey",
"Heimdall",
"Hel",
"Vidar",
"Vale",
"Harbard",
"Kvasir",
"Njord",
]
)
def __len__(self):
return len(self._students)
def __str__(self):
return ", ".join(self._students)
To generate a readable string for the attending students of the class, we return the names of all students separated by a comma in the __str__
method.
attendants = ClassAttendants()
print(attendants)
Balder, Frey, Freya, Frigg, Harbard, Heimdall, Hel, Kvasir, Loki, Njord, Odin, Thor, Vale, Vidar
By implementing the __str__
method, we're now able to print
our ClassAttendants
objects in a human-readable manner.
Get attendant(s) (__getitem__)
Getting an attending student via the index in the _students_
list can be achieved using the special method __getitem__
. This allows us to use the []
syntax on a ClassAttendants
object.
Just as with the len()
example, the list of students could be accessed directly, but as previously mentioned it's favourable to implement the special method. Furthermore, _students
is declared as a private variable and should therefore not be accessed directly.
As you can see in the code below, the __getitem__
method accepts a position
argument and returns the self._students
element at the corresponding position.
class ClassAttendants:
def __init__(self):
self._students = sorted(
[
"Odin",
"Frigg",
"Balder",
"Loki",
"Thor",
"Freya",
"Frey",
"Heimdall",
"Hel",
"Vidar",
"Vale",
"Harbard",
"Kvasir",
"Njord",
]
)
def __len__(self):
return len(self._students)
def __str__(self):
return ", ".join(self._students)
def __getitem__(self, position):
return self._students[position]
After implementing the __getitem__
method, the []
operator can be used on any ClassAttendants
object.
attendants = ClassAttendants()
attendants[0]
'Balder'
As __getitem__
passes the []
operator to the _students
list a slice
is a valid position
argument for __getitem__
as the list
data structure supports slices
.
This is useful when fetching more than multiple objects. For example, we might get the first five attending students.
attendants = ClassAttendants()
attendants[0:5]
['Balder', 'Frey', 'Freya', 'Frigg', 'Harbard']
Or we might get all students attending the class.
attendants = ClassAttendants()
attendants[:]
['Balder', 'Frey', 'Freya', 'Frigg', 'Harbard', 'Heimdall', 'Hel', 'Kvasir', 'Loki', 'Njord', 'Odin', 'Thor', 'Vale', 'Vidar']
Update attendant (__setitem__)
Sometimes, we need to update the object. For example, we might want to replace Freya
with Freja
(the Swedish spelling of Freya) on the 2nd position of the list of students who attended the class.
Let's try to do so.
attendants = ClassAttendants()
attendants[2] = "Freja"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'ClassAttendants' object does not support item assignment
Unfortunately, that didn't work. To update the _students
variable, we need to implement the special method __setitem__
, which closely resembles the __getitem__
method.
The difference between these two methods is that the __setitem__
method also accepts the value
parameter, which is the value to be assigned at the given position
.
By inspecting the two methods in the implementation below, pay close attention to their differences and I'm sure you'll find it quite easy to grasp.
class ClassAttendants:
def __init__(self):
self._students = sorted(
[
"Odin",
"Frigg",
"Balder",
"Loki",
"Thor",
"Freya",
"Frey",
"Heimdall",
"Hel",
"Vidar",
"Vale",
"Harbard",
"Kvasir",
"Njord",
]
)
def __len__(self):
return len(self._students)
def __str__(self):
return ", ".join(self._students)
def __getitem__(self, position):
return self._students[position]
def __setitem__(self, position, value):
self._students[position] = value
Now, let's try to update the 2nd position again.
attendants = ClassAttendants()
attendants[2] = "Freja"
print(attendants)
Balder, Frey, Freja, Frigg, Harbard, Heimdall, Hel, Kvasir, Loki, Njord, Odin, Thor, Vale, Vidar
As you can see, the attending student at position 2 has been updated from Freya to Freja.
Conclusion
In this article, we've touched on how special methods can enable you to write code that is more pythonic, hides implementation details, and enables users of your class to do more with less code.
I invite you to further explore how the use of special methods can enable you to write cleaner and better code, and improve your Python skills.