Understanding Python Magic (Dunder) Methods
This article explains Python's magic (dunder) methods, categorizing them by purpose—construction, representation, comparison, arithmetic, container behavior, callability, and context management—and provides clear code examples illustrating how to customize class behavior for more Pythonic, intuitive objects.
Magic methods (also called special methods or dunder methods) are a set of special functions in Python that start and end with double underscores (e.g., __init__ ). These methods are automatically invoked by the Python interpreter in specific situations without being called directly.
Magic methods provide powerful capabilities for Python's object‑oriented programming, allowing developers to customize class behavior so that instances behave like built‑in types.
Common Magic Method Categories
1. Construction and Initialization
__new__(cls[, ...]) : the first method called when creating an instance.
__init__(self[, ...]) : instance initialization method.
__del__(self) : destructor method, called when an instance is destroyed.
class MyClass:
def __new__(cls, *args, **kwargs):
print("__new__ called")
instance = super().__new__(cls)
return instance
def __init__(self, value):
print("__init__ called")
self.value = value
def __del__(self):
print("__del__ called")
obj = MyClass(10) # Output: __new__ called then __init__ called
del obj # Output: __del__ called2. Object Representation
__str__(self) : defines behavior for str() or print() .
__repr__(self) : defines behavior for repr() , usually for debugging.
__format__(self, format_spec) : defines behavior for format() .
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point({self.x}, {self.y})"
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
p = Point(1, 2)
print(str(p)) # Output: Point(1, 2)
print(repr(p)) # Output: Point(x=1, y=2)3. Comparison Operators
__eq__(self, other) : defines behavior for == .
__ne__(self, other) : defines behavior for != .
__lt__(self, other) : defines behavior for < .
__le__(self, other) : defines behavior for <= .
__gt__(self, other) : defines behavior for > .
__ge__(self, other) : defines behavior for >= .
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __eq__(self, other):
return self.score == other.score
def __lt__(self, other):
return self.score < other.score
s1 = Student("Alice", 90)
s2 = Student("Bob", 85)
print(s1 == s2) # Output: False
print(s1 < s2) # Output: False4. Arithmetic Operators
__add__(self, other) : defines behavior for + .
__sub__(self, other) : defines behavior for - .
__mul__(self, other) : defines behavior for * .
__truediv__(self, other) : defines behavior for / .
__floordiv__(self, other) : defines behavior for // .
__mod__(self, other) : defines behavior for % .
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(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Output: Vector(4, 6)5. Container Type Methods
__len__(self) : defines behavior for len() .
__getitem__(self, key) : defines element retrieval ( self[key] ).
__setitem__(self, key, value) : defines element assignment ( self[key] = value ).
__delitem__(self, key) : defines element deletion ( del self[key] ).
__contains__(self, item) : defines behavior for in operator.
class ShoppingCart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def __getitem__(self, index):
return self.items[index]
def __setitem__(self, index, value):
self.items[index] = value
def __delitem__(self, index):
del self.items[index]
def add_item(self, item):
self.items.append(item)
cart = ShoppingCart()
cart.add_item("Apple")
cart.add_item("Banana")
print(len(cart)) # Output: 2
print(cart[1]) # Output: Banana6. Callable Objects
__call__(self[, args...]) : makes an instance callable like a function.
class Adder:
def __init__(self, n):
self.n = n
def __call__(self, x):
return self.n + x
add5 = Adder(5)
print(add5(10)) # Output: 157. Context Management
__enter__(self) : defines behavior at the start of a with block.
__exit__(self, exc_type, exc_val, exc_tb) : defines behavior at the end of a with block.
class Timer:
def __enter__(self):
import time
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
self.end = time.time()
print(f"Elapsed: {self.end - self.start:.2f} seconds")
with Timer():
# Execute some code
sum(range(1000000))Practical Applications of Magic Methods
1. Implementing a Decorator Class
class Logger:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(f"Calling function: {self.func.__name__}")
result = self.func(*args, **kwargs)
print(f"Function returned: {result}")
return result
@Logger
def add(a, b):
return a + b
print(add(2, 3))
# Output:
# Calling function: add
# Function returned: 5
# 52. Implementing Attribute Access Control
class Protected:
def __init__(self):
self._protected_data = "protected data"
self.__private_data = "private data"
def __getattribute__(self, name):
print(f"Attempting to access attribute: {name}")
return super().__getattribute__(name)
def __setattr__(self, name, value):
print(f"Setting attribute: {name} = {value}")
super().__setattr__(name, value)
def __delattr__(self, name):
print(f"Deleting attribute: {name}")
super().__delattr__(name)
p = Protected()
print(p._protected_data)
p.new_attr = "new attribute"
del p.new_attr3. Implementing the Iterator Pattern
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
return self
def __next__(self):
if self.start <= 0:
raise StopIteration
current = self.start
self.start -= 1
return current
for num in Countdown(5):
print(num, end=" ")
# Output: 5 4 3 2 1Notes
Do not overuse magic methods; only implement them when you truly need to change default behavior.
Maintain consistency: when implementing comparison operators, provide the full set or use functools.total_ordering .
Performance considerations: magic method calls add overhead; be cautious in performance‑critical code.
Documentation: clearly describe custom magic method behavior in your class docs.
Summary
Python's magic methods give object‑oriented programming great flexibility, allowing developers to customize class behavior in many situations. By using them wisely, you can create intuitive, Pythonic classes, but they should be applied judiciously.
php中文网 Courses
php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.