Definition
A class you write always starts out poorer than Python's own types. Len, ==, a for loop all work on a dictionary or a list, never on a handmade object until you add it yourself. Magic methods close that gap by plugging your class into that same syntax.
A magic method is a method whose name starts and ends with two underscores, such as __len__ or __eq__. You never call it yourself: Python triggers it on your behalf as soon as a piece of language syntax meets your object. Writing len(basket) amounts to asking for basket.__len__(), and the class defining it gains a behaviour the built-in types have always had, as this basket shows: it answers len only because it defines __len__, nothing more.
class Basket:
def __init__(self, items):
self.items = items
def __len__(self):
# This method answers what len(basket) is asking for
return len(self.items)
basket = Basket(["pen", "notebook"])
print(len(basket))
# 2They are also called special methods, or dunder methods in most documentation, short for double underscore. The word magic only describes the effect felt, never the mechanism: it is a naming convention the interpreter knows by heart and checks at the right moment.
The families worth knowing
Python defines well over a hundred magic methods, a number that can feel overwhelming: do you need to know them all before writing a useful class? No, a dozen cover the essentials, gathered in the table below.
| Method | Triggered by |
|---|---|
__init__ | Creating an object |
__repr__ and __str__ | repr and display |
__eq__ | The equality operator |
__lt__ | Strict comparison, and therefore sorting |
__getitem__ | Square bracket access |
__contains__ | The membership test |
__iter__ | The for loop and anything that walks |
__enter__ and __exit__ | The with block |
__call__ | Parentheses placed after the object |
Sorting a list of objects calls in theory for several of these comparison methods. The total_ordering decorator from the functools module fills in the missing ones from __eq__ and just one other, for instance __lt__: two methods are then enough where six would otherwise be needed.
An object becomes walkable the moment it exposes __iter__, whether or not it inherits from a list. A context manager is nothing more than an object exposing __enter__ and __exit__, exactly what duck typing describes: what counts is not the class you inherit from, but what your object knows how to do when asked.
The trap of equality without hashing
Adding an __eq__ method looks like it commits you to nothing: you describe how two objects resemble each other. Yet that line carries a side effect almost nobody expects: Python silently removes the default hashing from the class, and your objects can no longer sit in a set or serve as a dictionary key.
class Client:
def __init__(self, email):
self.email = email
def __eq__(self, other):
# Two clients are equal if their email is, nothing else
return self.email == other.email
{Client("mary@example.com")}
# TypeError: unhashable type: 'Client'The error never shows up when you write __eq__, it waits for the day someone stores your objects in a set or a dict. The TypeError: unhashable type message then points to a line that has nothing to do with the real cause.
The fix is one more method, __hash__, computed on the same attributes as the equality. Python enforces that consistency: two equal objects must produce the same hash, otherwise a dictionary could store the same key twice without ever noticing. A dataclass declared with frozen=True generates both methods for you, and that is often the best answer.
Two representations for two readers
Without writing anything, printing one of your objects in the console returns a line like <Client object at 0x7f...>, which says nothing about what the object holds. Two magic methods let you take back control of that text, and they do not address the same reader.
__repr__ speaks to the developer: it shows up in the console, in error messages and when a list of objects is displayed, and it should reveal what the object is made of. __str__ speaks to the end user, and it is the one an ordinary display produces.
class Duration:
def __init__(self, minutes):
self.minutes = minutes
def __repr__(self):
# For debugging: show the raw value
return f"Duration(minutes={self.minutes})"
def __str__(self):
# For display: show a readable format
return f"{self.minutes // 60} h {self.minutes % 60}"If you write only one of them, write __repr__: Python falls back on it when __str__ is missing, whereas the reverse is never true. A class with no representation prints a memory address, which tells you nothing while debugging and turns a list of results into guesswork.
Frequently asked questions
Can a magic method be called directly?
Technically yes, basket.__len__() works. In practice, better never to: the language syntax raises a clear TypeError when the method is missing, whereas the direct call produces an attribute error that is far harder to make sense of.
How many should a class define?
As few as possible, and __repr__ nearly always since it helps debugging at no cost. A magic method only makes sense when the syntax it captures really says something about the object: giving addition to something nobody adds up in real life makes the code unreadable.
Why does my object refuse to be walked through by a loop?
Because it defines neither __iter__ nor __getitem__, the only two doors Python tries before giving up and raising an error.