Magic Methods (dunder methods)

Loading

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

MethodPurpose
__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

Leave a Reply

Your email address will not be published. Required fields are marked *