In Python, there is a special kind of methods affectionately called magic methods.
These are methods which name has two underscores as prefix and suffix. Because of this, they, even more, are known by the cute name dunder methods. Dunder here is simply a creative short name for these “double underscores” methods.
Here are a few examples for magic methods: __init__, __add__, __len__, __repr__ etc.
Now you might ask, what is so magical about these methods anyway? Well, one thing is that they are special in the sense that you don’t need to call them directly as you do for other methods: Python calls them for you when it needs to know how you want some specific things (object initialization, representation, etc) should be handled. Every object you create in Python usually has an init, and should also have a repr (representation) or str method. The repr is typically a functional representation of the object, whereas the str method tends to be for a "pretty print" version of the object.
class Person:
def __init__(self, name, surname):
self.name = name
self.surname = surname
>>> p = Person("John", "Smith")
>>> print(p)
<__main__.Person at 0x1110ceda0>
Not the prettiest output. Let's add a repr (special aka dunder) method.
class Person:
def __init__(self, name, surname):
self.name = name
self.surname = surname
def __repr__(self):
return f"{self.__class__.__name__}: {self.name.capitalize()} {self.surname.capitalize()}
>>> dan = Person('murphy', 'daniel')
>>> print(dan)
Person: Daniel Murphy
That said, I should also add that if __repr__ is defined, and __str__ is not, the object will behave as though __str__ = __repr__. It’s a good practice to always implement at least the __repr__ dunder method for any class you use. Note that once the __str__ is implemented, it will be used during calls to print() and str() functions on the object of the class while __repr__ will be called when you call repr() on these objects.
__repr__: the goal of __repr__ is to be unambiguous. This is a geek-representation of the object that can be very helpful during debugging.
__str__: the goal of __str__ is to be readable for the user to know the object without too much problem. It’s purpose is to be nice to read while presenting the object.
We can view the difference here:
class Person:
def __init__(self, name, surname):
self.name = name
self.surname = surname
def __str__(self):
return f"{self.name.capitalize()} {self.surname.capitalize()}
def __repr__(self):
return f"class {self.__class__.__name__}: {self.name} {self.surname}
>>> dan = Person('daniel', 'murphy')
>>> dan
'class Person: daniel murphy'
>>> print(dan)
'Daniel Murphy'
>>> print(dan.__repr__())
'class Person: daniel murphy'
You can change the meaning of an operator in Python depending upon the operands used. Python operators work for built-in classes. But the same operator behaves differently with different types. For example, the + operator will perform arithmetic addition on two numbers, merge two lists, or concatenate two strings.
This feature in Python that allows the same operator to have different meaning according to the context is called operator overloading.
So what happens when we use them with objects of a user-defined class? What if we want to be able to use the "+" operator with class Person? Well- first we have to decide what it would mean to "add" people. Let's take the approach that Star Trek used with the Dax Symbiote- so that if we were to add 'Joran Belar' with 'Torias Dax', we would get a result of 'Joran Dax'. Here's how we can do it:
class Person:
def __init__(self, name, surname):
self.name = name
self.surname = surname
def __str__(self):
return f"{self.name.capitalize()} {self.surname.capitalize()}
def __repr__(self):
return f"class {self.__class__.__name__}: {self.name} {self.surname}
def __add__(self, other):
return Person(self.name, other.surname)
>>> p1 = Person('Joran', 'Belar')
>>> p2 = Person('Torias', 'Dax')
>>> print(p1, p2)
Joran Belar Torias Dax
>>> p3 = p1 + p2
>>> print(p3)
Joran Dax
There are a lot of dunder methods we can use- if we wish- in our classes.
Note that we can implement all or any of the examples here in the same way that we implemented the add operator overloading.
Tutorialspoint has a good example here and GeeksForGeeks has a similar example.
Some of the examples on this page are inspired by this where you can also find how to make a class iterable using the len and getitem methods (which are also dunder methods).