Dunders in Python

Posted on Fri 06 September 2019 in Python • 3 min read

A 'dunder' (double underscores) in Python (also known as a magic method) are the functions within classes having two prefix and suffix underscores in the function name. These are normally used for operator overloading (eg, __init__, __add__, __len__, __repr__, etc). For this post we will build a customized class for vectors to understand how the magic methods can be used to make life easier.

First of all before we get into the magic methods, let's talk about normal methods. A method in Python is a function that resides in a class. To begin with our Vector class, we initialise our class and give it a function, for example:

1
2
3
4
class Vector():

    def say_hello():
        print("Hello! I'm a method")

Now to call the method, we simply call the function name along with the Vector instance we wish to use:

1
Vector.say_hello()

This will print:

1
Hello! I'm a method

Now for our vector class, we want to be able to initialise it with certain constants or variables for both the magnitude and direction of our vector. We use the __init__ magic method for this, as it is invoked without any call, when an instance of a class is created.

1
2
3
class Vector():
    def __init__(self, *args):
        self.values = args

Now when we create an instance of our Vector class, we can give it certain values that it will store in a tuple:

1
2
3
vector_1 = Vector(1,2,3)

print(vector_1)

Which will print:

1
<__main__.Vector object at 0x03E90530>

But to us humans, this doesn't mean much more than we know what the name of the class is of that instance. What we really want to see when we call print on our class is the values inside it. To do this we use the __repr__ magic method:

1
2
3
4
5
6
7
8
9
class Vector():
    def __init__(self, *args):
        self.values = args
    def __repr__(self):
        return str(self.values)

vector_1 = Vector(1,2,3)

print(vector_1)

Which will print:

1
(1, 2, 3)

This is exactly what we want! Now what if we wanted to create a Vector, but we weren't sure what values we wanted to give it yet. What would happen if we didn't give it any values? Would it default to (0,0) like we would hope?

1
2
3
empty_vector = Vector()

print(empty_vector)

Which will print:

1
()

Not exactly how we need it, so we would need to run a check when the class is being initialized, to ensure that there are values being provided:

1
2
3
4
5
6
7
8
class Vector():
    def __init__(self, *args):
        if len(args) == 0:
            self.values = (0,0)
        else:
            self.values = args
    def __repr__(self):
        return str(self.values)

Which when initialise an empty instance of our Vector now, it will create a (0,0) vector for us!

Now what if we wanted to be able to check how many values were inside our vector class? To do this we can use the __len__ magic method>:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Vector():
    def __init__(self, *args):
        if len(args) == 0:
            self.values = (0,0)
        else:
            self.values = args
    def __repr__(self):
        return str(self.values)
    def __len__(self):
        return len(self.values)

vector_1 = Vector(1,2,3)

print(vector_1)
print(len(vector_1))

Which will print:

1
2
(1, 2, 3)
3

Hopefully this post has given you insight into how dunders/magic methods could be used to super power your classes and make life much easier!

You can find more information and examples about dunders in Python at: <https://docs.python.org/3/reference/datamodel.html#special-method-names