Dunder/Magic Methods in Python
Tags: python, programming, tutorial, codenewbie
Published: 2021-03-29 15:00:27

<!-- wp:separator -->
<hr class="wp-block-separator"/>
<!-- /wp:separator -->

<!-- wp:heading {"level":4} -->
<h4>Dunder methods are names that are preceded and succeeded by double underscores, hence the name dunder. They are also called magic methods and can help override functionality for built-in functions for custom&nbsp;classes.</h4>
<!-- /wp:heading -->

<!-- wp:heading {"level":3} -->
<h3>1. Introduction</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Implementing dunder methods for classes is a good form of Polymorphism. If you have ever created a class in Python and used the init function, then you have already been using dunder methods.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>2. Table of&nbsp;contents</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>1. Introduction</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>2. Table of Contents</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>3. Prerequisites</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>4. Why do we need Dunder Methods?</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>5. Our custom class</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>6. Dunder Methods for our class</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>7. Some more dunder methods</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>8. Conclusion</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>3. Prerequisites</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Before we continue it will be important to have the following:</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>A basic understanding of Object Oriented Programming using Python.</li><li>Experience working with classes in Python.</li><li>Familiarity with built-in functions such as len, get, set, etc.</li></ul>
<!-- /wp:list -->

<!-- wp:heading {"level":3} -->
<h3>4. Why do we need Dunder&nbsp;methods?</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Consider a case where we have the following class:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">class point:<br>    x = 4<br>    y = 5</code></pre>
<!-- /wp:code -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">p1 = point()<br>print(p1)</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The print statement would print something like <code>&lt;__main__.point object at 0x7fb992998d00&gt;</code>. But, we might want the print statement to display something in the format <code>(4,10)</code>. We can achieve this by overriding the <code>__str__</code> method of our class.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>We could also override other methods such as the <code>len, +, []</code> etc. We will create a new class and override many of the built-in functions in this article.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>5. Our custom&nbsp;class</h3>
<!-- /wp:heading -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">class softwares:<br>    names = []<br>    versions = {}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>This class will be used to save a list of software and their versions. <code>names</code> is a list to store the names of the software and <code>versions</code> is a dictionary where the key is the software name and the value is the version number. By default, all softwares start with a version of 1.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>6. Dunder methods for our&nbsp;class</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Before moving on, please ensure that your indentation is correct. The methods that will be discussed below are methods belonging to the class we created and must be indented appropriately.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>6.1. init</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>This is a method you must have already used if you have worked with classes. The <code>init</code> method is used to create an instance of the class.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">def __init__(self,names):<br>    if names:<br>        self.names = names.copy()<br>        for name in names:<br>            self.versions[name] = 1<br>    else:<br>        raise Exception("Please Enter the names")</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The <code>init</code> method defined above accepts a list of names as parameters and stores it in the class’ <code>names</code> list. Additionally, it also populates the <code>versions</code> dictionary. We have also put a check on the <code>names</code> list.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If the list is empty, an exception is raised. Below is how we would use the <code>init</code> method.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">p = softwares(['S1','S2','S3'])<br>p1 = softwares([])</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The first statement would work fine but the second line would raise an exception since an empty list was passed in as a parameter.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>6.2. str</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The <code>str</code> method is useful when we want to use instances of our class in a print statement. As discussed earlier, it usually returns a memory object. But we can override the <code>str</code> method to meet our requirements.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">def __str__(self):<br>    s ="The current softwares and their versions are listed below: \n"<br>    for key,value in self.versions.items():<br>        s+= f"{key} : v{value} \n"<br>    return s</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The above <code>str</code> method returns the software and their versions. Ensure that the function returns a string. Below is how we would call the method.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">print(p)</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":4} -->
<h4>6.3. setitem</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>When assigning values in a dictionary, the <code>setitem</code> method is invoked.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">d = {}<br>d['key'] = value</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>We can give instances of our class a similar feature with the help of the <code>setitem</code> method.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">def __setitem__(self,name,version):<br>    if name in self.versions:<br>        self.versions[name] = version<br>    else:<br>        raise Exception("Software Name doesn't exist")</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The method above is going to update the version number of the software. If the software is not found, it will raise an error.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In the 3rd line, we use the built-in <code>setitem</code> method of a dictionary.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>We can invoke the <code>setitem</code> method in the following way:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">p['S1'] = 2<br>p['2'] = 2</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The first line would update the version of software S1 to 2. But the second line would raise an exception since software 2 doesn’t exist.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>6.4. getitem</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The <code>getitem</code> method is like the <code>setitem</code> method, the major difference being that the <code>getitem</code> method is called when we use the <code>[]</code> operator of a dictionary.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">d = {'val':key}<br>print(d['val'])</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Instances of our class can also be given a similar feature.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">def __getitem__(self,name):<br>    if name in self.versions:<br>        return self.versions[name]<br>    else:<br>        raise Exception("Software Name doesn't exist")</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The above method essentially returns the version of the software. If the software is not found, it raises an exception. To invoke the <code>getitem</code> method, we can write the following line of code.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">print(p['S1'])<br>print(p['1'])</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The first line would print the version of S1. But, the second line would raise an Exception since 1 doesn’t exist.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>6.5. delitem</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The <code>delitem</code> is like the <code>setitem</code> and <code>getitem</code> method. To avoid repetition, we will move on to the implementation and use case.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">def __delitem__(self,name):<br>    if name in self.versions:<br>        del self.versions[name]<br>        self.names.remove(name)<br>    else:<br>        raise Exception("Software Name doesn't exist")</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The <code>delitem</code> method deletes the software from the dictionary as well as the list.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>It can be used as follows.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">del p['S1']</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":4} -->
<h4>6.6. len</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>In a dictionary, the <code>len</code> method returns the number of elements in a list or the number of key-value pairs in a dictionary.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>We can define a <code>len</code> method for our class as well.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">def __len__(self):<br>    return len(self.names)</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The <code>len</code> method for our class returns the number of softwares. As you might have noticed, we are using the built-in <code>len</code> method of a list to return the number of software.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The <code>len</code> method of our class can be used in the following way.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">print(len(p))</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":4} -->
<h4>6.7. contains</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The <code>contains</code> method is used when using the <code>in</code> operator. The return value has to be a boolean.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">def __contains__(self,name):<br>    if name in self.versions:<br>        return True<br>    else:<br>        return False</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The method checks if the name is found in the dictionary. We will be using the dictionary’s built-in <code>contains</code> method for that.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">if 'S2' in p:<br>    print("Software Exists")<br>else:<br>    print("Software DOESN'T exist")</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The code above prints the statement inside the if blocks since software S2 is present inside the <code>versions</code> dictionary.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>6.8. Complete&nbsp;code</h4>
<!-- /wp:heading -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">class softwares:<br>    names = []<br>    versions = {}<br>    <br>    def __init__(self,names):<br>        if names:<br>            self.names = names.copy()<br>            for name in names:<br>                self.versions[name] = 1<br>        else:<br>            raise Exception("Please Enter the names")<br>    <br>    def __str__(self):<br>        s ="The current softwares and their versions are listed below: \n"<br>        for key,value in self.versions.items():<br>            s+= f"{key} : v{value} \n"<br>        return s<br>    <br>    def __setitem__(self,name,version):<br>        if name in self.versions:<br>            self.versions[name] = version<br>        else:<br>            raise Exception("Software Name doesn't exist")<br>    <br>    def __getitem__(self,name):<br>        if name in self.versions:<br>            return self.versions[name]<br>        else:<br>            raise Exception("Software Name doesn't exist")<br>    <br>    def __delitem__(self,name):<br>        if name in self.versions:<br>            del self.versions[name]<br>            self.names.remove(name)<br>        else:<br>            raise Exception("Software Name doesn't exist")<br>    <br>    def __len__(self):<br>        return len(self.names)<br>    <br>    def __contains__(self,name):<br>        if name in self.versions:<br>            return True<br>        else:<br>            return False</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3>7. Some more dunder&nbsp;methods</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Before looking at some more dunder methods, let’s create a new class.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">class point:<br>    x = None<br>    y = None<br>    <br>    def __init__(self, x , y):<br>        self.x = x<br>        self.y = y<br>    <br>    def __str__(self):<br>        s = f'({self.x},{self.y})'<br>        return s</code></pre>
<!-- /wp:code -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">p1 = point(5,4)<br>p2 = point(2,3)</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>We have created a class point which is basically a 2D point. The class has an <code>init</code> method and a <code>str</code> method. We have also created a couple of instances of the class.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>7.1. add</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The <code>add</code> method is called when using the <code>+</code> operator. We can define a custom <code>add</code> method for our class.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><code>p1 + p2</code> is equal to <code>p1._add__(p2)</code></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">def __add__(self,p2):<br>    x = self.x + p2.x<br>    y = self.y + p2.y<br>    return point(x,y)</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The above method adds the x and y coordinates of the first instance of <code>point</code> and the second instance of <code>point</code>. It will create a new instance of <code>point</code> and then return it.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">p3 = p1 + p2</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The line of code above invokes the <code>add</code> method.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>7.2. iadd</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The <code>iadd</code> method is like the <code>add</code> method. It is invoked when using the <code>+=</code> operator</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">def __iadd__(self,p2):<br>    self.x += p2.x<br>    self.y += p2.y<br>    return self</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The method above just updates an instance’s coordinates by adding the coordinates of <code>p2</code>. Make sure you are returning <code>self</code>, otherwise it will return None and won’t work as expected.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">p1 += p2<br>print(p1)</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The above method invokes the <code>iadd</code> method.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>7.3. Other operators</h4>
<!-- /wp:heading -->

<!-- wp:list -->
<ul><li><code>__sub__(self,p2)</code> ( - )</li><li><code>__isub__(self,p2)</code> ( -= )</li><li><code>__mul__(self,p2)</code> ( * )</li><li><code>__imul__(self,p2)</code> ( *= )</li><li><code>__truediv__(self,p2)</code>( \ )</li><li><code>__itruediv__(self,p2)</code> ( \= )</li><li><code>__floordiv__(self,p2)</code> ( \\ )</li><li><code>__ifloordiv__(self,p2)</code> ( \= )</li></ul>
<!-- /wp:list -->

<!-- wp:heading {"level":4} -->
<h4>7.4. call</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>When invoking a function like <code>func()</code>, we are invoking the <code>call</code> method.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If we put in place a <code>call</code> method for our class, we can do the following:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">p1()<br>p2()</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Below is an example call method:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">def __call__(self):<br>    print(f"Called Point {self.x},{self.y}")</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":4} -->
<h4>7.5. Complete&nbsp;code</h4>
<!-- /wp:heading -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="">class point:<br>    x = None<br>    y = None<br>    <br>    def __init__(self, x , y):<br>        self.x = x<br>        self.y = y<br>    <br>    def __str__(self):<br>        s = f'({self.x},{self.y})'<br>        return s<br>    <br>    def __add__(self,p2):<br>        print("In add")<br>        x = self.x + p2.x<br>        y = self.y + p2.y<br>        return point(x,y)<br>    <br>    def __iadd__(self,p2):<br>        self.x += p2.x<br>        self.y += p2.y<br>        return self<br>    <br>    def __isub__(self,p2):<br>        self.x -= p2.x<br>        self.y -= p2.y<br>        return self<br>    <br>    def __imul__(self,p2):<br>        self.x *= p2.x<br>        self.y *= p2.y<br>        return self<br>    <br>    def __itruediv__(self,p2):<br>        self.x /= p2.x<br>        self.y /= p2.y<br>        return self<br>    <br>    def __ifloordiv__(self,p2):<br>        self.x //= p2.x<br>        self.y //= p2.y<br>        return self<br>    <br>    def __call__(self):<br>        print(f"Called Point {self.x},{self.y}")</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3>8. Conclusion</h3>
<!-- /wp:heading -->

Dunder methods are indeed magical and can help you improve the functionality of your class. They can help you customize your classes and re-define the built-in methods. Although we have discussed some of the most common dunder methods, there are more dunder methods like __hash__ which are really useful. __hash__ is used to add a hashing algorithm to your class. This allows objects of your class to be used as keys in a dictionary and have a lookup time of O(1). You can find more about __hash__ and other dunder methods <a href= 'https://docs.python.org/3/reference/datamodel.html#'>here</a>.

<!-- wp:paragraph -->
<p>Happy Learning!&nbsp;:)</p>
<!-- /wp:paragraph -->