BlogsDope image BlogsDope

Python Dunder and Magic Methods

Dec. 15, 2020 PYTHON FUNCTION 2167

Python Dunder Methods In Python, there is a set of special methods, called ‘Dunder’ methods, which is derived from ‘double-under’, since they use double underscores as prefix and suffix, like in __init__ or __len__ . Often, these methods are also referred to as ‘magic methods’, making them look more complicated, although there is really nothing magical about them. At the end of the day – they are just another Python feature.

These so-called Dunders give us the possibility to emulate built-in behavior, in other words, to change the built-in functions to behave the way we want them to. To understand these magic methods, let’s go through some important keywords:

  1. Class – Category of things that have common attributes or properties
  2. Object – Single instance of the class, able to perform the functionalities from the class
  3. Self – The key to the attributes and methods in the class
  4. __init__ - Initialization of the attributes in a class, also called a constructor in object-oriented concepts, invoked when an object is created
  5. Overriding – Giving a new value to an attribute that already has value

Now let’s try to code something simple: class Vehicle:

class Vehicle:
    # simple class called vehicle
    def __init__(self, wheels=2):
        # constructing the vehicle, supposing that it has at least two wheels
        self.wheels = wheels
    # setting the class attribute wheels to a value from the object

bicycle = Vehicle()  # new object, 2 wheels by default
car = Vehicle(4)  # another object, 4 wheels passed as a parameter

Explanation:

__init__ can be also referred as a constructor of the class as it is the method which runs automatically whenever a new object is created.

So far, we created a class vehicle we instantiated a parameter wheels, with a default value of 2. Then we created two objects, from which one has a parameter. If we pass an integer in the object, it will override the number of wheels, like in the object ‘car’. The __init__ method takes care of the object creation. If we try to print the ‘car’ object:

<__main__.Vehicle object at 0x7f0c884a5410>

This returns the class name, pointing that this is an object from it and its current address in memory. What if we want to print the number of wheels? We can add the function __str__, this allows us to make a printable version of an object. Let’s see this in code:

class Vehicle:
    # simple class called vehicle

    def __init__(self, wheels=2):
        # constructing the vehicle, supposing that it has at least two wheels
        self.wheels = wheels
        # setting the class attribute wheels to a value from the object

    def __str__(self):
        # printable object method
        return "Number of wheels: {}".format(self.wheels)
        # print the number of wheels


car = Vehicle(4)  # new object, with a passed value
print(str(car))  # calling the overridden function str, so we can print the object

Every built-in method has a dunder method. In fact, all logical operators have a built-in method, for example: ‘+’ has a correspondent method called __add__, ‘-’ has __sub__ and they can also be overloaded. You can take a look at method overloading for more details. Let’s list through some of the most important special methods.

__new__
Called to create a new instance of a class
__del__
Called when an instance is about to be deleted
__repr__
Called to print an object, useful for debugging; used as a fallback to str
__format__
Called to format string literals
__len__
​Called to return the length of an object
__lt__
​Less then, replaces < operator
__le__
​Less equals, replaces <= operator
__eq__
Equals, replaces == operator
__ne__
Not equals, replaces != operator
__gt__
Greater than, replaces > operator
__ge__
Greater than or equals to, replaces >= operator
__mul__
Multiplication, replaces * operator 
__truediv__
​Division, replaces / operators
__getitem__
​Used to introduce value indexing, requires parameter key
__setitem__
Used to make items mutable, requires parameter key, items can be accessed through index
__delitem__
Similar to del, used to delete items through index​
__iter__
Returns an iterator to iterate the values in a container

Of course, there are many more dunder methods and these are just a part of the whole Python built-in method list. To get the hang of it, it requires practice. Also, you can list all the dunder methods with code. To explore we can use the __dict__ method, simply type this:

print(list.__dict__)

According to Python documentation, every object has an attribute, which is denoted in __dict__. This dictionary method is also called mappingproxy. We will go through the __dict__ method in the following example.

Now let’s imagine that we want to create a school class that contains details about student grades. What we also want, is an easy way to count out the students. Let’s code this.

class Student:
    def __init__(self, ids, name):
        self.ids, self.name = ids, name

    def __str__(self):
        return f"Student: {self.name}, ID: {self.ids}"


# class Student, requires ID and Name of student, uses str() to return the student


class School:
    def __init__(self, students, grades):
        self.students, self.grades = students, grades

    def __len__(self):
        return len(self.students)


# class School, requires student – uses object from class Student, and grades, len() returns the number of students

students = [
    Student(1, "Billy"),
    Student(2, "Ann"),
    Student(3, "Chris"),
]  # creating object students
grades = ["C-", "B", "A+"]  # creating the grades for each student

school = School(students, grades)  # creating object School with the created values
print(len(school))  # returning the number of students in the school

This works, but what if we want to manage the student grades? So far, students are not accessible, let’s try __setitem__ and __getitem__.

class Student:
    def __init__(self, ids, name):
        self.ids, self.name = ids, name

    def __str__(self):
        return f"Student: {self.name}, ID: {self.ids}"


class School:
    def __init__(self, student, grades):
        self.student, self.grades = student, grades

    def __len__(self):
        return len(self.student)

    # ADDED: Change the grade of a certain student
    def __setitem__(self, key, value):
        self.grades[key] = value

    # ADDED: Return the grade from a certain student
    def __getitem__(self, key):
        return self.student[key].name, self.grades[key]


students = [Student(1, "Billy"), Student(2, "Ann"), Student(3, "Chris")]
grades = ["C-", "B", "A+"]

school = School(students, grades)
print(len(school))

print(school[0])  # Prints the 0th student, name and grade

school[0] = "B"  # Billy got a ‘B’ now!
print(school[0])  # Prints the 0th student, name and grade again, only updated.

Output

​3
('Billy', 'C-')
('Billy', 'B')

Now, let’s call the __dict__ method from the class names. 

print(School.__dict__)
print(Student.__dict__)

{'__module__': '__main__', '__init__': <function School.__init__ at 0x7fe920594560>, '__len__': <function School.__len__ at 0x7fe9205945f0>, '__setitem__': <function School.__setitem__ at 0x7fe920594680>, '__getitem__': <function School.__getitem__ at 0x7fe920594710>, '__dict__': <attribute '__dict__' of 'School' objects>, '__weakref__': <attribute '__weakref__' of 'School' objects>, '__doc__': None}

{'__module__': '__main__', '__init__': <function Student.__init__ at 0x7fe920594440>, '__str__': <function Student.__str__ at 0x7fe9205944d0>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}

This method allows us to observe the functionalities built in the classes. It is also possible to call the dict method to access specific functions, simply by typing:

print(School.__dict__['__init__'])

and the output is:

<function School.__init__ at 0x7f4bc1b8b560>

This is another useful feature in Python that allows users to analyze huge classes easily. Having all these ‘tricks up the sleeve’ is exceptionally important when a problem occurs. To master the dunder methods, you need to implement code on your own. Certain things can only be learned through typing code. These magic methods are extremely useful to make a class interactive. They make the class compatible with the built-in methods. Another bonus is that you don’t need to remember the name of the function, you just remember the built-in methods. This way, Python is consistent with its syntax.  


Liked the post?
Super excited, extremely motivated and highly dedicated in terms of Python and Artificial Intelligence!
Editor's Picks
0 COMMENT

Please login to view or add comment(s).