![]()
Magic methods, also called dunder (double underscore) methods, are special methods in Python that allow us to define how objects of a class behave in different situations. They are prefixed and suffixed with double underscores (e.g., __init__, __str__, __add__).
Magic methods enable operator overloading, object customization, and built-in function support.
1. Constructor and Destructor (__init__, __del__)
1.1 __init__ – Object Initialization (Constructor)
This method is called when an object is created. It initializes instance attributes.
class Car:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def display(self):
print(f"Car: {self.brand} {self.model}")
car1 = Car("Toyota", "Corolla") # Calls __init__()
car1.display()
Why use __init__?
- Automatically initializes attributes when an object is created.
1.2 __del__ – Object Destruction (Destructor)
Called when an object is about to be deleted or goes out of scope.
class Car:
def __init__(self, brand):
self.brand = brand
print(f"{self.brand} car created.")
def __del__(self):
print(f"{self.brand} car destroyed.")
car1 = Car("Honda")
del car1 # Calls __del__()
Why use __del__?
- Cleans up resources when an object is deleted.
2. String Representation (__str__, __repr__)
2.1 __str__ – User-Friendly String Representation
Used when calling print() or str() on an object.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"Person: {self.name}, Age: {self.age}"
p = Person("Alice", 30)
print(p) # Calls __str__()
Why use __str__?
- Provides a human-readable description of the object.
2.2 __repr__ – Official String Representation
Used for debugging and should return a string that can recreate the object.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f'Person("{self.name}", {self.age})'
p = Person("Bob", 25)
print(repr(p)) # Calls __repr__()
Why use __repr__?
- Used for debugging and should return a string that can recreate the object.
3. Operator Overloading (__add__, __sub__, __mul__, etc.)
3.1 __add__ – Overloading + Operator
Allows objects to be added using +.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(4, 5)
v3 = v1 + v2 # Calls __add__()
print(v3) # Output: Vector(6, 8)
Why use __add__?
- Enables custom addition behavior for objects.
3.2 __sub__, __mul__, __truediv__ – Overloading Other Operators
class Number:
def __init__(self, value):
self.value = value
def __sub__(self, other):
return Number(self.value - other.value)
def __mul__(self, other):
return Number(self.value * other.value)
def __truediv__(self, other):
return Number(self.value / other.value)
def __str__(self):
return f"Number({self.value})"
n1 = Number(10)
n2 = Number(5)
print(n1 - n2) # Calls __sub__() → Output: Number(5)
print(n1 * n2) # Calls __mul__() → Output: Number(50)
print(n1 / n2) # Calls __truediv__() → Output: Number(2.0)
Why use these methods?
- Allow mathematical operations on user-defined objects.
4. Comparison Overloading (__eq__, __lt__, __gt__)
Used to compare objects with <, >, ==, etc.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def __eq__(self, other): # Overloads ==
return self.salary == other.salary
def __lt__(self, other): # Overloads <
return self.salary < other.salary
def __gt__(self, other): # Overloads >
return self.salary > other.salary
emp1 = Employee("Alice", 5000)
emp2 = Employee("Bob", 7000)
print(emp1 == emp2) # Calls __eq__() → Output: False
print(emp1 < emp2) # Calls __lt__() → Output: True
print(emp1 > emp2) # Calls __gt__() → Output: False
Why use comparison methods?
- Allow objects to be compared logically.
5. Length and Indexing (__len__, __getitem__, __setitem__)
5.1 __len__ – Returns Length of an Object
class Team:
def __init__(self, members):
self.members = members
def __len__(self):
return len(self.members)
team = Team(["Alice", "Bob", "Charlie"])
print(len(team)) # Calls __len__() → Output: 3
5.2 __getitem__, __setitem__ – Indexing and Assignment
class Team:
def __init__(self, members):
self.members = members
def __getitem__(self, index):
return self.members[index]
def __setitem__(self, index, value):
self.members[index] = value
team = Team(["Alice", "Bob", "Charlie"])
print(team[1]) # Calls __getitem__() → Output: Bob
team[1] = "David" # Calls __setitem__()
print(team.members) # Output: ['Alice', 'David', 'Charlie']
Why use these methods?
- Enable custom indexing and slicing for objects.
6. Summary of Magic Methods
| Method | Purpose |
|---|---|
__init__ | Initializes an object (constructor) |
__del__ | Cleans up resources when an object is deleted |
__str__ | Provides a user-friendly string representation |
__repr__ | Provides an official string representation |
__add__, __sub__, __mul__, etc. | Overloads arithmetic operators |
__eq__, __lt__, __gt__, etc. | Overloads comparison operators |
__len__ | Returns length of an object |
__getitem__, __setitem__ | Enables indexing and assignment |
