# Dunder or Magic Methods in Python with Examples

## Introduction to Dunder or Double Underscore

Python data models is a mean by which you can implement protocols, and those protocols have abstract meaning depending on the object itself.

And these python data models are generally implemented using Double Underscore Methods. Double underscore methods are also known as Magic Methods or Dunder Methods.

These methods are generally used for operator overloading in python. These methods are very important in python and because of these methods we can add two complex number, concatenate two strings, ... without any typecasting.

Examples of Dunder Methods: `__init__(), __repr__(), __str__(), __del__(), __format__(), __bytes__(), __hash__(), __len__(), __add__(), __call__(), ...`

## Understanding Dunder or Magic Methods

To understand these double underscore methods, let's start with simple class and its instances that looks like:

``````
# defining Polynomial class
class Polynomial:
pass

# creating two Polynomial
p1 = Polynomial()
p2 = Polynomial()

# setting Polynomial coefficients
p1.coeffs = 1,2,3 #x^2 + 2x +3
p2.coeffs = 3,4,5 #3x^2 + 4x + 3

# printing coefficients
print(p1.coeffs)
print(p2.coeffs)
``````

Output

```p1 =  (1, 2, 3)
p2 =  (3, 4, 5)
```

So, what are we doing here? We just created class named `Polynomial` that has no any properties and methods right now. And, we created two instances of polynomials, namely, `p1 & p2`. After creating polynomials instances, we have set their coefficients. Now Polynomial p1 = x2 + 2x + 3 and p2 = 3x2 + 4x + 5. That's it. Nothing more!

But, this is a long procedure right? 4 lines (except class and printing section) of code! we can reduce it to two lines of code using dunder method `__init__()`.

## Implementing __init__() Method

``````
# defining polynomial class
class Polynomial:
def __init__(self, *coeffs):
self.coeffs = coeffs

# constructing or initializing Polynomial with coefficients
p1 = Polynomial(1,2,3)
p2 = Polynomial(3,4,5)

# printing coefficients
print('p1 = ', p1.coeffs)
print('p2 = ', p2.coeffs)
``````

Output

```p1 =  (1, 2, 3)
p2 =  (3, 4, 5)
```

We just added our first data model that handles initialization and construction of objects.

Now, let's look at the following piece of code:

``````
# defining polynomial class
class Polynomial:
def __init__(self, *coeffs):
self.coeffs = coeffs

# constructing or initializing Polynomial with coefficients
p1 = Polynomial(1,2,3)
p2 = Polynomial(3,4,5)

# printing class representation
print(p1)
print(p1)
``````

Output

```<__main__.Polynomial object at 0x0316C5E0>
<__main__.Polynomial object at 0x031CD430>
```

Wait, what is this output? Do you know, what exactly is this? This is just showing class name with some sort of memory location as a description of objects, right? So, what do we missing here is representation of object data model or dunder method i.e. `__repr__()`.

## Implementing __repr__() Method

``````
# defining polynomial class
class Polynomial:
def __init__(self, *coeffs):
self.coeffs = coeffs

def __repr__(self):
return 'Polynomial({!r})'.format(self.coeffs)

# constructing or initializing Polynomial with coefficients
p1 = Polynomial(1,2,3)
p2 = Polynomial(3,4,5)

# printing class representation
print(p1)
print(p2)
``````

Output

```Polynomial((1, 2, 3))
Polynomial((3, 4, 5))
```

We just added another dunder or magic function or data models successfully which is the representation of objects. Now for the sake of completeness let's add another method `__add__` which overloads the '+' operator.

Now, let's look at the following piece of code:

``````
# defining polynomial class
class Polynomial:
def __init__(self, *coeffs):
self.coeffs = coeffs

def __repr__(self):
return 'Polynomial({!r})'.format(self.coeffs)

# constructing or initializing Polynomial with coefficients
p1 = Polynomial(1,2,3)
p2 = Polynomial(3,4,5)

print(p1+p2)
``````

Output

```Traceback (most recent call last):
File "C:/Users/erame/Desktop/Mission Python/dunder.py", line 14, in <module>
print(p1+p2)
TypeError: unsupported operand type(s) for +: 'Polynomial' and 'Polynomial'
```

Error! So, what are we missing here? As error says, unsupported operand + for 'Polynomial'. And yes, we are missing the data model or magic function to Polynomial class that what happens when these polynomials are added together i.e. `__add__()` dunder method.. That is what we need to implement now!

``````
# defining polynomial class
class Polynomial:
def __init__(self, *coeffs):
self.coeffs = coeffs

def __repr__(self):
return 'Polynomial({!r})'.format(self.coeffs)

return Polynomial(*(x+y for x,y in zip(self.coeffs,other.coeffs)))

# constructing or initializing Polynomial with coefficients
p1 = Polynomial(1,2,3)
p2 = Polynomial(3,4,5)

```Polynomial((4, 6, 8))
So after adding `__add__()` magic methods we can use `+` operator to add these polynomials. Similarly, you can add bunch of these magic methods to your class as required. To known what magic method you can implement we encourage you to go through See full list in python documentation.