Python magic methods, often referred to as dunder methods (short for "double underscore"), are special methods that enable customization of Python's built-in operations. For instance, when you write a + b
, Python internally calls a.__add__(b)
. This is one of many magic methods that can be implemented in your custom classes to enable various behaviors.
__init__(self, ...)
: Initializes an object after it's created.__str__(self)
: Defines how the object should be represented as a string (used by print()
and str()
).__repr__(self)
: Returns a detailed representation of the object, useful for debugging.class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def __str__(self):
return f"'{self.title}' by {self.author}"
def __repr__(self):
return f"Book(title={self.title!r}, author={self.author!r})"
book = Book("1984", "George Orwell")
print(book) # Output: '1984' by George Orwell
print(repr(book)) # Output: Book(title='1984', author='George Orwell')
You can control how attributes are accessed, set, and deleted by defining these methods:
__getattr__(self, name)
: Invoked when an attribute is accessed that does not exist.__getattribute__(self, name)
: Controls access to all attributes.__setattr__(self, name, value)
: Controls how attributes are set.__delattr__(self, name)
: Defines what happens when an attribute is deleted.class Person:
def __init__(self, name):
self.name = name
def __getattr__(self, item):
return f"{item} attribute not found."
def __setattr__(self, name, value):
if name == 'name' and not value.isalpha():
raise ValueError("Name must contain only letters.")
super().__setattr__(name, value)
person = Person("John")
print(person.age) # Output: age attribute not found.
person.name = "J0hn" # Raises ValueError: Name must contain only letters.
Magic methods can be used to overload arithmetic operators like +
, -
, *
, etc.
__add__(self, other)
: Called for +
operator.__sub__(self, other)
: Called for -
operator.__mul__(self, other)
: Called for *
operator.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 __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(1, 5)
print(v1 + v2) # Output: Vector(3, 8)
Magic methods for comparison include:
__eq__(self, other)
: For ==
.__lt__(self, other)
: For <
.__le__(self, other)
: For <=
.class Box:
def __init__(self, size):
self.size = size
def __eq__(self, other):
return self.size == other.size
def __lt__(self, other):
return self.size < other.size
box1 = Box(10)
box2 = Box(20)
print(box1 == box2) # Output: False
print(box1 < box2) # Output: True
To make your objects behave like containers (e.g., lists, dictionaries), implement:
__len__(self)
: For len()
.__getitem__(self, key)
: For indexing.__setitem__(self, key, value)
: For setting values.__delitem__(self, key)
: For deleting items.class CustomList:
def __init__(self):
self.data = []
def __len__(self):
return len(self.data)
def __getitem__(self, index):
return self.data[index]
def __setitem__(self, index, value):
self.data[index] = value
def __delitem__(self, index):
del self.data[index]
lst = CustomList()
lst.data = [1, 2, 3]
print(len(lst)) # Output: 3
print(lst[0]) # Output: 1
lst[1] = 10
del lst[2]
print(lst.data) # Output: [1, 10]
To enable an object to be used in a with
statement, define:
__enter__(self)
: Called when the context is entered.__exit__(self, exc_type, exc_value, traceback)
: Called when the context exits, even if an exception is raised.class Resource:
def __enter__(self):
print("Acquiring resource")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("Releasing resource")
with Resource():
print("Using resource")
# Output:
# Acquiring resource
# Using resource
# Releasing resource
__str__
and __repr__
to provide meaningful string representations of your objects.__enter__
and __exit__
for classes that require setup and teardown operations, such as file handling, database connections, etc.a < b
is True, then b > a
should also be True).__eq__
without __hash__
: If you override __eq__
, consider also overriding __hash__
to ensure the object behaves correctly in hash-based collections like sets or as dictionary keys.__getattribute__
and __getitem__
can be called frequently. Ensure they are optimized to avoid performance bottlenecks.__getattr__
and __getattribute__
: __getattr__
should only be used for attributes that don't exist, and __getattribute__
should be used with caution to avoid infinite loops.