The Dunders(Magic Methods) in Python
In the Python community, Dunder is a shortened word for Double Underscores(__), which are often used to define special methods.
Names or methods having both leading and trailing underscores(e.g. __samplename__) refers to special methods defined in the language. These methods are implicitly called under the hood.
This guide introduces some of them
1. __init__ : the __init__ method is mostly used in class declaration in python. It can be used as a constructor to initialize attributes specific
to each instance of a class. This method is automatically called when we attempt to create a real object(instance) from a Class(blueprint/template)
Aside its wide usage as a constructor, it can be utilized in turning a dictionary into an object where we can reference its key as an attribute of the object.
Let’s take a look at this simple scenario.
Say we have a python dictionary:
my_dict = {"name":"Ray", "email":"a@gmail.com", "user_id":1}
To access the value of a field in the dictionary, we would need to write my_dict[‘name’] or my_dict.get(‘name’)
Turning the dictionary into an object(instance)
[*]Snippet
class UserData:
def __init__(self, my_dict):
for key in my_dict:
setattr(self, key, my_dict[key])
#setattr(x, ‘y’, v) is equivalent to x.y = v
[*]Usage
my_dict = {"name":"Ray", "email":"a@gmail.com", "user_id":1}
user = UserData(my_dict)
print(user.name)
print(user.email)
print(user.user_id)
[*]Output
Ray
a@gmail.com
1
2. __call__: Recall that whenever we need to call a function, we would write as ‘name_of_function()’, what happens under the hood whenever you call a function is that the __call__ method provided by python get executed. What this means is that if we need to make an object callable(i.e. being able to be invoked or called), we need to implement the __call__ method.
In the previous code snippet, we created an object user:
[*]
user = UserData(my_dict)
Any attempt to call user() would lead to an error saying is not callable. To fix this, we can define the __call__ method
[*]Snippet
class UserData:
def __init__(self, my_dict):
for key in my_dict:
setattr(self, key, my_dict[key])
def __call__(self):
return 5
[*]Usage
my_dict = {"name":"Ray", "email":"a@gmail.com", "user_id":1}
user = UserData(my_dict)
print("Calling user::", user())
[*]Output
Calling user:: 5
3. __add__: This method is called when using + operator. When you say 1+2, it executes __add__ method provided by python. This translates to sum for numbers and concatenation for string. This leads us to another concept called ‘operator overloading’.
What this means is that whenever we create our own custom object, we can overload any operator(+,-,*,/, etc.). such that when the operator in used with our objects, __add__(+) or __sub__(-) which we have defined in our class would be executed.
[*] +
num_1 = 2
num_1 + 1
[*]Output
3
[*] __add__
num_1 = 2
num_1.__add__(1)
[*]Output
3
4. __str__: This is used to define the nicely printable string representation of an object of a class. It’s default behavior is to return the memory address of an object but can be overridden to meet our requirement.
[*]Snippet
class User:
pass
user1 = User()
print(user1)
[*]Output
<__main__.User object at 0x7fc47284fb20>
Let’s override the __str__ method:
[*]Snippet
class User:
pass
def __str__(self) -> str:
return "New user object"
user1 = User()
print(user1)
[*]Output
New user object
5. __new__: When a class is instantiated, __new__ method is called just before __init__ method. The __new__ method does the object creation while __init__ does initialization(i.e. setting attributes for the object). One particular use case of this method is when implementing singleton design pattern. It checks if an instance of the class does not exist before creating any other.
Singleton design pattern ensures that only one instance of a class exists.
[*]Snippet
class Singleton:
_instance = None
def __new__(cls):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
__new__(cls) is a class method. Link to explanation on methods
[*]Usage
s1 = Singleton()
s2 = Singleton()
s3 = Singleton()
print(s1)
print(s2)
print(s3)
[*]Output
<__main__.Singleton object at 0x7f611939fd30>
<__main__.Singleton object at 0x7f611939fd30>
<__main__.Singleton object at 0x7f611939fd30>
As we can see, s1,s2, and s3 points to the same object because the memory addresses(0x7f611939fd30) are the same. What you will be on your computer may be different but it will remain the same for all on them
There are many other special methods, commonly know as ‘dunders’, available in Python, these are just a few examples.
Thanks for reading and feel free to explore my video collection on YouTube for more educational content. Don’t forget to subscribe to the channel to stay updated with future releases.