Fundamentals 19 min read

Understanding Python Magic (Dunder) Methods: Definitions, Examples, and Best Practices

This article explains Python magic (dunder) methods, their purposes, common examples such as __init__, __str__, __repr__, __len__, __getitem__, __setitem__, __delitem__, __iter__, __call__, __add__, __eq__, __hash__, and shows how to implement operator overloading, iterator and context‑manager protocols while discussing naming rules, performance, inheritance, type checking, and limitations.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Understanding Python Magic (Dunder) Methods: Definitions, Examples, and Best Practices

Python magic methods (also called special or dunder methods) are methods whose names start and end with double underscores. The interpreter automatically calls them in specific situations, allowing custom classes to behave like built‑in types.

Common magic methods and examples

1. __init__(self, ...) – called when initializing an object.

class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Alice", 30)

2. __str__(self) – returns a readable string representation, used by print() .

class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"Person(name={self.name}, age={self.age})"
p = Person("Alice", 30)
print(p)  # 输出: Person(name=Alice, age=30)

3. __repr__(self) – returns an official string representation, useful for debugging.

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("Alice", 30)
print(repr(p))  # 输出: Person('Alice', 30)

4. __len__(self) – returns the length of the object.

class MyList:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
my_list = MyList([1, 2, 3, 4])
print(len(my_list))  # 输出: 4

5. __getitem__(self, key) – enables indexing access (e.g., obj[key] ).

class MyDict:
def __init__(self, data):
self.data = data
def __getitem__(self, key):
return self.data[key]
my_dict = MyDict({"name": "Alice", "age": 30})
print(my_dict["name"])  # 输出: Alice

6. __setitem__(self, key, value) – enables indexed assignment (e.g., obj[key] = value ).

class MyDict:
def __init__(self, data):
self.data = data
def __setitem__(self, key, value):
self.data[key] = value
my_dict = MyDict({"name": "Alice", "age": 30})
my_dict["age"] = 31
print(my_dict.data)  # 输出: {'name': 'Alice', 'age': 31}

7. __delitem__(self, key) – enables deletion (e.g., del obj[key] ).

class MyDict:
def __init__(self, data):
self.data = data
def __delitem__(self, key):
del self.data[key]
my_dict = MyDict({"name": "Alice", "age": 30})
del my_dict["age"]
print(my_dict.data)  # 输出: {'name': 'Alice'}

8. __iter__(self) – returns an iterator object, making the object iterable.

class MyRange:
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.start < self.end:
current = self.start
self.start += 1
return current
else:
raise StopIteration
for num in MyRange(1, 5):
print(num)  # 输出: 1 2 3 4

9. __call__(self, *args, **kwargs) – makes an instance callable like a function.

class Adder:
def __init__(self, value):
self.value = value
def __call__(self, x):
return self.value + x
add_five = Adder(5)
print(add_five(10))  # 输出: 15

10. __add__(self, other) – overloads the + 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 __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)  # 输出: Vector(4, 6)

11. __eq__(self, other) – defines equality comparison.

class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2)  # 输出: True

12. __hash__(self) – returns a hash value, usually paired with __eq__ so objects can be used as dictionary keys.

class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
p1 = Point(1, 2)
p2 = Point(1, 2)
d = {p1: "value"}
print(d[p2])  # 输出: value

Operator Overloading

By defining methods like __add__ , you can control how the + operator works for custom classes.

class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
else:
raise TypeError("Unsupported operand type(s) for +: 'Vector' and '{}'".format(type(other).__name__))
def __str__(self):
return f"({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(4, 5)
result = v1 + v2
print(result)  # 输出: (6, 8)

Sequence Protocol

Implement __getitem__ , __setitem__ , and __delitem__ to make objects behave like lists or dictionaries.

class SimpleDict:
def __init__(self, data=None):
self.data = data or {}
def __getitem__(self, key):
return self.data[key]
sd = SimpleDict({"key": "value"})
print(sd["key"])  # 输出: value

Iterator Protocol

Define __iter__ and __next__ to make an object iterable.

class Fibonacci:
def __init__(self, n):
self.n = n
self.current = 0
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
if self.current < self.n:
result = self.a
self.a, self.b = self.b, self.a + self.b
self.current += 1
return result
else:
raise StopIteration
fib = Fibonacci(10)
for num in fib:
print(num, end=' ')
# 输出: 0 1 1 2 3 5 8 13 21 34

Context‑Manager Protocol

Implement __enter__ and __exit__ to use objects with the with statement.

class ManagedFile:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
with ManagedFile('hello.txt', 'w') as f:
f.write('Hello, world!')

Limitations and Best Practices

Magic methods must follow the double‑underscore naming convention; only those recognized by the interpreter are invoked automatically.

Be aware of performance overhead for frequently called methods like __getattr__ or __setattr__ .

When overriding in subclasses, call the parent implementation if needed to preserve expected behavior.

Perform proper type checking inside magic methods to avoid unexpected TypeError s.

Ensure consistency: __len__ returns a non‑negative integer, __str__ returns a string, etc.

Do not rely on __del__ for critical cleanup; its execution is not guaranteed.

When implementing __eq__ , also implement a compatible __hash__ if objects are to be hashable.

In summary, magic methods provide a powerful way to integrate custom types with Python’s built‑in features, while ordinary functions serve to implement specific business logic; understanding both is essential for writing idiomatic, Pythonic code.

Pythonoperator overloadingSpecial MethodsContext ManagerIterator ProtocolMagic Methods
Test Development Learning Exchange
Written by

Test Development Learning Exchange

Test Development Learning Exchange

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.