Magic methods in Python (also known as dunder methods, short for “double underscore”) are special methods with double underscores before and after their names, like __init__
and __str__
. They allow you to define behaviors for built-in operations on your custom objects. Magic methods enable customization of common operations such as adding, printing, or comparing objects, making code cleaner and more intuitive.
some of the most commonly used magic methods:
Initialization (__init__
)
The __init__
method is the constructor in Python. It’s called when an object is created, allowing you to initialize attributes.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("John", 35) # __init__ is called here
print(p.name) # Output: John
String Representation (__str__
and __repr__
)
__str__
: Defines the informal string representation of an object, shown when using print
or str()
.
__repr__
: Defines the official string representation, aimed at developers for debugging. It’s called by repr()
and in the interactive shell.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name}, {self.age} years old"
def __repr__(self):
return f"Person('{self.name}', {self.age})"
p = Person("John", 35)
print(str(p)) # Output: John, 35 years old
print(repr(p)) # Output: Person('John', 35)
Arithmetic Operators (__add__
, __sub__
, etc.)
Magic methods allow objects to interact with basic arithmetic operations.
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"({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(4, 5)
print(v1 + v2) # Output: (6, 8)
Comparison Operators (__eq__
, __lt__
, __gt__
, etc.)
These magic methods allow you to define custom behavior for comparison operations.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.age == other.age
def __lt__(self, other):
return self.age < other.age
p1 = Person("John", 30)
p2 = Person("Bob", 25)
print(p1 == p2) # Output: False
print(p1 < p2) # Output: False
Length (__len__
)
Defines behavior for the len()
function.
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
def __len__(self):
return self.pages
book = Book("My Python", 350)
print(len(book)) # Output: 350
Attribute Access (__getattr__
, __setattr__
, __delattr__
)
Magic methods for managing attributes dynamically.
__getattr__
: Called when trying to access an attribute that doesn’t exist
__setattr__
: Controls how attributes are set.
__delattr__
: Controls how attributes are deleted.
class Person:
def __init__(self, name):
self.name = name
def __getattr__(self, attr):
return f"{attr} not found."
p = Person("John")
print(p.age) # Output: age not found.
Callable (__call__
)
Makes an instance of a class callable like a function.
class Greeter:
def __init__(self, name):
self.name = name
def __call__(self, greeting):
return f"{greeting}, {self.name}!"
greet = Greeter("John")
print(greet("Hello")) # Output: Hello, John!
Context Management (__enter__
and __exit__
)
Used for managing resources with with
statements, like file handling or database connections.
class FileHandler:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename, 'w')
return self.file
def __exit__(self, exc_type, exc_value, traceback):
self.file.close()
with FileHandler("test.txt") as file:
file.write("Hello, World!")
Iterator Protocol (__iter__
and __next__
)
These methods allow an object to be used as an iterator.
class Counter:
def __init__(self, max):
self.max = max
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current < self.max:
self.current += 1
return self.current
raise StopIteration
counter = Counter(3)
for num in counter:
print(num) # Output: 1 2 3