Dunder methods
Magic methods or dunder methods in Python are those that begin and end with __
, such as __init__
. The name dunder comes from double underscore.
These are methods defined by Python that can be used internally or associated with operators. For example:
- If you use
()
under the hood it calls__init__
. - If you use
+
,__add__
is called. - If you use
==
,__eq__
is called.
Depending on what you want to do and your class, you may want to define these dunder methods. This is very powerful, as it allows you to define the behavior of your class.
Next, we will see the most known ones. We will use as an example a Point
class with two attributes x
and y
. This class represents a point in space in two dimensions.
Method __init__
. As we have seen previously, it is the constructor and allows us to create the object. It is called when using ()
.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(p.x) # 1
print(p.y) # 2
Method __del__
. It can be seen as the opposite of __init__
. One constructs, one destroys. So you can implement the logic of your destructor.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __del__(self):
print("Object destroyed")
This method will be called when you destroy your object with del
. You decide what to put there. It is common to see it in places where it is important to free resources after you are done.
p = Point(1, 2)
del p
# Object destroyed
__str__
method. If we print
our object, the information displayed is not very useful.
p = Point(1, 2)
print(p)
# <__main__.Point object at 0x102b5a100>
If we define __str__
, when using print
, it will display whatever we want. It is useful to define this method in your classes, as it helps to see the content of the object.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"x={self.x} y={self.y}"
p = Point(1, 2)
print(p) # x=1 y=2
Methods __add__
and __sub__
. Imagine you want to add +
or subtract -
two objects. These methods define the addition and subtraction logic.
- β We define the sum of two points as the sum of the coordinates
x
on one side andy
on the other. - β We define the subtraction of two points as the subtraction of the coordinates
x
on one side andy
on the other.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Point(self.x - other.x, self.y - other.y)
def __str__(self):
return f"x={self.x} y={self.y}"
Now that we have defined both operators, we can use them. If you do not define them, you will get TypeError
.
p1 = Point(5, 10) + Point(3, 1)
p2 = Point(-1, 0) - Point(-5, 0)
print(p1) # x=8 y=11
print(p2) # x=4 y=0
Methods __eq__
and __ne__
. These two methods allow us to define when two objects are equal or different. We consider two objects equal if their coordinates are equal.
class Point:
# ...
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __ne__(self, other):
return not self == other
equal = Point(1, 1) == Point(1, 1)
print(equal)
# True
You can check that they are equal. There are also other methods such as __lt__
or __gt__
that are used to define when one object is less <
or greater >
than another.
print(Point(1, 1) == Point(1, 1))
# True
Methods __iter__
and __next__
. These methods allow us to make a class iterable, that is, we can use it with a for
. One defines the iterator object, and the other defines the next number to iterate. We define a class that iterates even numbers up to a limit
value.
class Evens:
def __init__(self, limit):
self.i = 0
self.limit = limit
def __iter__(self):
return self
def __next__(self):
if self.i >= self.limit * 2:
raise StopIteration
even = self.i
self.i += 2
return even
We can generate the first 5 even numbers as follows.
for i in Evens(5):
print(i)
# 0, 2, 4, 6, 8
Methods __enter__
and __exit__
. They are related to the context manager and we will see them in more detail in the chapter on exceptions. For now, it is enough to know that they allow us to execute an action when entering a block with
and another one when exiting. They make a kind of sandwich.
class ContextManager:
def __enter__(self):
print("Enter")
return None
def __exit__(self, exc_type, exc_value, traceback):
print("Exit")
with ContextManager() as f:
print("Inside")
# Enter
# Inside
# Exit
Having seen the most important ones, let us now look at a practical application using the dunder __add__
method seen earlier.
Imagine we have a Currency
class that can store values in USD and EUR. A priori, it is not possible to add USD with EUR, since we cannot mix apples and oranges.
However, we can define __add__
so that a conversion to USD is performed. Once both values have been converted, they can be summed.
class Currency:
rates = {'USD': 1.0, 'EUR': 1.10}
def __init__(self, currency, amount):
self.currency = currency
self.amount = amount
def convert(self, new_currency):
if self.currency != new_currency:
return self.amount * Currency.rates[self.currency] / Currency.rates[new_currency]
return self.amount
def __add__(self, other):
if isinstance(other, Currency):
amount_usd = self.convert('USD') + other.convert('USD')
return Currency('USD', amount_usd)
raise TypeError("Error")
def __str__(self):
return f'{self.currency} {self.amount:.2f}'
Now we can add different currencies, as an automatic conversion is performed.
expense1 = Currency('USD', 5.25)
expense2 = Currency('EUR', 7.99)
print(expense1 + expense2)
# USD 14.04
There are other dunder methods that we invite you to review, but we have covered the main ones that will suffice for most of your programs.