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
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
Or, you can override the __init__
method:
class Car:
def __init__(self, color) -> None:
self.color = color
obj = Car("red")
print(obj.color) #red
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>
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'
Now, the print statement outputs:
print(obj) # a red car
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
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
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)
Now, the length of my object corresponds to its color attribute's length:
obj = Car("red")
print(len(obj)) # 3
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
Now, adding two Car objects looks like this:
obj = Car("red")
obj2 = Car("blue")
print(obj + obj2) # a red car and a blue car
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 = {}
__setitem__
will create a key-value pair inside our attributes dictionary:
def __setitem__(self, key, value):
self.attributes[key] = value
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
Now, operations like these are possible:
obj = Car("red")
obj["model"] = "Honda civic"
print(obj["model"]) # Honda civic
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)