DEV Community

Soukaina Tyes
Soukaina Tyes

Posted on • Updated on

Python's magic methods

Python's magic methods, or dunder methods (short for "double underscore," because they start and end with double underscores) are special methods that enable certain functionalities within Python's classes, such as operator overloading.
So here are some of Python's most important dunder methods:

1- __init__

syntax: object.__init__(self[, ...])

We will start with the constructor itself, Consider a class called Car:

class Car:
    pass
Enter fullscreen mode Exit fullscreen mode

To add the attribute 'color' to this class, you can do it after instantiating an object like this:

obj = Car()

obj.color = "red"

print(obj.color)  ##red
Enter fullscreen mode Exit fullscreen mode

Or, you can override the __init__ method:

class Car:

def __init__(self, color) -> None:

    self.color = color

obj = Car("red")

print(obj.color) #red
Enter fullscreen mode Exit fullscreen mode

Now, you can instantiate an object with the attribute value set from the start.

2- __str__:

syntax: object.__str__(self)

By default, printing an object of our class Car yields:

print(obj) # <__main__.Car object at 0x10445ee90>
Enter fullscreen mode Exit fullscreen mode

This output, which combines the module where the class is defined (__main__) and the object's memory address in hexadecimal (0x10445ee90), is not very helpful. To modify it for readability, override the __str__ method:

...
def __str__(self) -> str:
    return f'a {self.color} car'
Enter fullscreen mode Exit fullscreen mode

Now, the print statement outputs:

print(obj) # a red car
Enter fullscreen mode Exit fullscreen mode

3- __eq__:

syntax: object.__eq__(self, other)

Creating two objects with the same color attribute and checking if they're equal yields:

obj = Car("red")

obj2 = Car("red")

print(obj == obj2) # False
Enter fullscreen mode Exit fullscreen mode

the default value of their equality is False because Python compares the memory address of the objects, to base the equality on another metric like their color override the __eq__ method:

def __eq__(self, other: object) -> bool:

    if (isinstance(other, Car)): # this to make sure the other object is of the same class first
        if other.color == self.color:
            return True
        return False
Enter fullscreen mode Exit fullscreen mode

4- __len__:

syntax: object.__len__(self)

By default, calling the len built-in function on an object of our class results in a TypeError, stating: object of type 'Car' has no len().
To give our class a len() implementation, define __len__:

def __len__(self) -> None:
    return len(self.color) 
Enter fullscreen mode Exit fullscreen mode

Now, the length of my object corresponds to its color attribute's length:

obj = Car("red")
print(len(obj)) # 3
Enter fullscreen mode Exit fullscreen mode

5- __add__:

syntax: object.__add__(self, other)

By default, attempting to add two objects of type Car results in a TypeError, stating that the operation is unsupported. To define specific behavior for the + operator, implement __add__:

def __add__(self, other):
    if (isinstance(other, Car)):
        return f"{str(self)} and {str(other)}" #the return value of str() when called upon an object of our class is the same as the __str__ method
    raise TypeError("objects are not of the same type") # raising a typeError if the objects are not both instances of Car
Enter fullscreen mode Exit fullscreen mode

Now, adding two Car objects looks like this:

obj = Car("red")

obj2 = Car("blue")

print(obj + obj2) # a red car and a blue car  
Enter fullscreen mode Exit fullscreen mode

the same goes for the other operators:

  • __ne__(self, other): Defines behavior for the inequality operator, !=
  • __lt__(self, other): Defines behavior for the less than operator, <
  • __gt__(self, other): Defines behavior for the greater than operator, >
  • __le__(self, other): Defines behavior for the less than or equal to operator, <=
  • __ge__(self, other): Defines behavior for the greater than or equal to operator, >=
  • __add__(self, other): Defines behavior for the addition operator, +
  • __sub__(self, other): Defines behavior for the subtraction operator, -
  • __mul__(self, other): Defines behavior for the multiplication operator, *
  • __truediv__(self, other): Defines behavior for the division operator, /

6- __getitem__ and __setitem__:

syntax:

  • object.__getitem__(self, key)
  • object.__setitem__(self, key, value)

to add and access attributes using the syntax obj[attribute]
use __getitem__ and __setitem__.

First, we'll modify the __init__ method to include an attributes dictionary where we can store additional attributes:

class Car:
    def __init__(self, color) -> None:
        self.color = color
        self.attributes = {}
Enter fullscreen mode Exit fullscreen mode

__setitem__ will create a key-value pair inside our attributes dictionary:

  def __setitem__(self, key, value):
    self.attributes[key] = value
Enter fullscreen mode Exit fullscreen mode

and __getitem__ will return the value for the key if found, or None if not:

def __getitem__(self, key):
    try:
        value = self.attributes[key] # try catch block to return a value of the attributes dictionary contains key, else return none

        return value
    except:
        return None
Enter fullscreen mode Exit fullscreen mode

Now, operations like these are possible:

obj = Car("red")

obj["model"] = "Honda civic"

print(obj["model"]) # Honda civic
Enter fullscreen mode Exit fullscreen mode

For more information on dunder methods and specifications, here's Python's official documentation: https://docs.python.org/3/reference/datamodel.html#special-method-names

Top comments (0)