Python operator overloading through magic methods is the feature that lets your custom classes work with the same built-in operators and functions that Python's native types use. When you write a plus sign between two integers, Python calls a special method behind the scenes to perform the addition. When you write an equality comparison between two strings, Python calls a different special method to determine whether they are equal. These special methods, called magic methods or dunder methods because their names start and end with double underscores, are the interface between Python's syntax and your objects' behavior. By implementing the right magic methods on your class, you can make your objects addable with the plus sign, comparable with equality operators, iterable in for loops, usable with the len function, and displayable with print, all through the same syntax that every Python developer already knows.
Operator overloading is not about making your code look clever. It is about making your classes feel like natural parts of the Python language. When a Vector class supports addition with the plus operator, code that adds vectors reads like mathematics. When a Money class supports comparison operators, code that checks whether one amount is greater than another reads like a business rule. The syntax matches the semantics, and anyone reading the code can understand what it does without looking up which method name performs which operation. The article on duck typing in Python covered how Python cares about what methods an object has rather than what type it is. Magic methods are the most important methods for this kind of structural compatibility, because they determine how your objects interact with the core language.
This article covers the most commonly used magic methods for operator overloading. It does not catalog every magic method in Python, of which there are over a hundred. Instead, it focuses on the methods you will actually implement in everyday classes: string representation, arithmetic operators, comparison operators, and container-like behavior. The next article in this section covers additional magic methods for object comparison and representation in more depth.
String representation with str and repr
The two most frequently implemented magic methods are the ones that control how your objects appear as strings. The str method is called by the built-in str function and by print. It should return a human-readable description of the object, suitable for displaying to an end user. The repr method is called by the built-in repr function and by the interactive Python interpreter when you type an expression and press Enter. It should return an unambiguous description of the object, ideally one that could be used to recreate it.
Here is a simple class that implements both string representation methods:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __repr__(self):
return f"Point({self.x}, {self.y})"The str method returns a clean coordinate pair that a user would expect to see. The repr method returns a string that looks like the Python code that would create an equivalent Point object. If you type Point(3, 7) in an interactive Python session, the interpreter calls repr and displays Point(3, 7). This convention helps with debugging because you can copy the repr output and paste it back into Python to recreate the object.
If you only implement one of the two, implement repr. Python falls back to repr when str is not defined, but the reverse is not true. The repr output will serve as both the human-readable and the developer-facing representation, which is acceptable for classes where the distinction between the two is not important.
Arithmetic operators for numeric classes
If your class represents something numeric, a mathematical vector, a monetary amount, a duration, a measurement with units, implementing the arithmetic magic methods lets you use the standard operators instead of named methods. Writing v1 + v2 is clearer than writing v1.add(v2), and it lets your objects participate in expressions that mix built-in types and custom types naturally.
The basic arithmetic methods come in pairs: a forward version that is called when your object is on the left side of the operator, and a reverse version that is called when your object is on the right side and the left object does not know how to handle the operation. Here is a simplified Vector class that supports addition:
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})"The add method is called when Python encounters v1 + v2. It receives self as the left operand and other as the right operand, and it returns a new Vector representing the sum. The original vectors are not modified, which is the expected behavior for arithmetic operators in Python: they produce new objects rather than mutating existing ones.
For in-place operators like +=, implement the corresponding in-place magic method. The iadd method is called for += and should modify the object in place and return self. If you do not implement iadd, Python falls back to add, which creates a new object and assigns it to the variable. This fallback behavior is usually acceptable, but implementing iadd explicitly gives you control over whether the operation mutates or creates.
Comparison operators for ordering and equality
Comparison magic methods let your objects respond to equality, inequality, and ordering operators. The eq method defines what it means for two objects of your class to be equal. The lt method defines less-than ordering. Python provides methods for all six comparison operators, but you do not need to implement all of them. The functools.total_ordering decorator can fill in the missing comparisons if you implement eq and one ordering method, though for simple cases implementing them directly is clearer.
Here is a class that represents a version number and supports equality and ordering comparisons. The comparison methods return NotImplemented when the other object is not a Version, which tells Python to try alternative comparison strategies:
class Version:
def __init__(self, major, minor, patch):
self.major = major
self.minor = minor
self.patch = patch
def __eq__(self, other):
if not isinstance(other, Version):
return NotImplemented
return (self.major, self.minor, self.patch) == (
other.major, other.minor, other.patch)The eq method compares version tuples for equality by comparing the major, minor, and patch components as a combined tuple. The ordering method follows the same comparison pattern but checks less-than ordering instead of equality:
def __lt__(self, other):
if not isinstance(other, Version):
return NotImplemented
return (self.major, self.minor, self.patch) < (
other.major, other.minor, other.patch)The comparison methods return NotImplemented when the other object is not a Version, which tells Python to try the reverse comparison or fall back to the default identity comparison. This pattern is important for interoperability with other types. Without the isinstance check, comparing a Version to an unrelated type would raise an AttributeError rather than returning False or allowing Python to try alternative comparison strategies.
Container-like behavior with len and getitem
If your class represents a collection of items, implementing the length and item access magic methods lets it work with the len function and with square bracket indexing syntax. A class that implements len can be passed to any function that calls len on its argument. A class that implements getitem can be indexed with square brackets and can be iterated over in a for loop, because Python's iteration protocol falls back to integer-indexed access when no iterator method is defined.
The following class wraps a list and provides only the container-like magic methods, without inheriting from list:
class Playlist:
def __init__(self, songs):
self._songs = list(songs)
def __len__(self):
return len(self._songs)
def __getitem__(self, index):
return self._songs[index]
def __contains__(self, song):
return song in self._songsA Playlist object can be passed to len, can be indexed with playlist[0], can be checked for membership with the in operator, and can be iterated over in a for loop. It provides the full container interface without exposing the list methods that would allow callers to modify the internal song list directly. This is the composition pattern applied to container design: the class contains a list but controls exactly which operations are exposed through magic methods.
The article on instance methods in Python covers the mechanics of defining methods that operate on object state. Magic methods are instance methods like any other, and they receive self as their first parameter. The only thing that makes them special is that Python calls them automatically in response to syntax, which is a powerful design choice that blurs the line between user-defined classes and built-in types.
Rune AI
Key Insights
- Magic methods are special methods with double underscore names that Python calls automatically in response to operators and built-in functions.
- Arithmetic operators like plus and minus map to methods like add and sub; comparison operators map to eq, lt, and friends.
- str provides a human-readable string representation; repr provides an unambiguous developer-facing representation.
- Implement only the magic methods that make semantic sense for your class; do not add arithmetic operators to non-numeric types.
- Magic methods let your custom classes integrate with Python's syntax, making them feel as natural to use as built-in types.
Frequently Asked Questions
What are Python magic methods and how do they enable operator overloading?
Which magic methods should I implement for my class?
What is the difference between __str__ and __repr__?
Conclusion
Operator overloading through magic methods is what makes Python's object model feel seamless. When your custom classes support the same operators and built-in functions as Python's native types, code that uses your classes reads naturally and integrates with the broader Python ecosystem. The key is to implement only the methods that make semantic sense for your class and to follow the established conventions for each method's behavior and return value.
More in this topic
Create Custom Iterators in Python
Learn how to build your own iterators in Python by implementing the __iter__() and __next__() methods, with practical examples and reusable patterns.
Python Iterator Protocol Explained
Learn how Python's iterator protocol works with the __iter__() and __next__() methods, and understand the contract every iterator must follow.
Python Iterables and Iterators Explained
Learn what iterables and iterators are in Python, how they power for loops under the hood, and why understanding them unlocks cleaner data processing patterns.