Python Magic Methods: The Complete __dunder__ Guide
Think of a TV remote control. You press the volume button, and the TV gets louder. You press the channel button, and it switches channels. You don't think about how it works — you just press the button and the TV responds.
Python has its own set of "buttons" it presses on your objects. When you write print(obj), Python presses the __str__ button. When you write len(obj), it presses the __len__ button. When you write obj == other, it presses __eq__. These buttons are called magic methods (or dunder methods, short for "double underscore").
By defining these methods in your class, you teach Python how your objects should respond to built-in operations. By the end of this tutorial, your custom objects will work as naturally as Python's built-in types.
What Are Magic Methods?
Magic methods are special methods whose names start and end with double underscores, like __init__, __str__, and __len__. You've already used __init__ — Python calls it automatically when you create a new object.
The key idea is that Python's built-in functions and operators are secretly calling these methods. You never call them directly. Instead, you define them, and Python calls them for you at the right time.
That ugly output happens because we haven't told Python how to display a Dog. Let's fix that with __str__.
What Is the Difference Between __str__ and __repr__?
__str__ defines the "nice" human-readable version of your object. It's what print() and str() use. Think of it as what you'd show to a user.
__repr__ defines the "official" developer version. It's what you see in the Python shell and in error messages. Ideally, it should look like valid Python code that could recreate the object.
How Do __len__ and __bool__ Work?
__len__ tells Python what len(obj) should return. It must return a non-negative integer. Any object with __len__ can be passed to the len() function.
__bool__ tells Python whether your object is "truthy" or "falsy." This is used in if statements, while loops, and boolean contexts. If you don't define __bool__, Python falls back to __len__ — an object with length 0 is falsy.
How Do __eq__ and __lt__ Enable Comparisons?
By default, == checks if two objects are the exact same object in memory (like is). That's almost never what you want. __eq__ lets you define what "equal" actually means for your class.
__lt__ (less than) is the foundation for sorting. Once you define it, Python can sort a list of your objects using sorted() or .sort().
How Do __getitem__ and __contains__ Work?
__getitem__ lets your object support bracket notation like obj[0] or obj['key']. It's what makes lists, dicts, and strings indexable.
__contains__ powers the in operator. When you write item in obj, Python calls obj.__contains__(item). If you don't define it but have __getitem__, Python will iterate through your object looking for the item.
What Does __call__ Do?
__call__ lets you use an object like a function. When you write obj(), Python calls obj.__call__(). This is useful for objects that represent some kind of action or transformation.
How Do Magic Methods Work Together in Practice?
Let's build a ShoppingCart class that combines several magic methods into one cohesive object.
Practice Exercises
Create a Book class with title and author attributes. Add __str__ that returns '<title> by <author>' and __repr__ that returns 'Book(\'<title>\', \'<author>\')'. Create Book('1984', 'Orwell') and print it using both str() and repr().
Create a Team class that stores a list of members (passed to __init__). Add __len__ to return the number of members. Create a team with ['Alice', 'Bob', 'Carol'] and print its length. Then create an empty team Team([]) and print whether it's truthy using bool().
This Coordinate class has a broken __eq__ method. Two coordinates with the same x and y should be equal, but they aren't. Find and fix the bug.
Create a Gradebook class that stores a dictionary of grades (passed to __init__). Add __getitem__ so you can look up a student's grade with bracket notation. Add __contains__ so you can check if a student is in the gradebook with in. Create a gradebook with {'Alice': 95, 'Bob': 82}, print Alice's grade using brackets, and print whether 'Carol' is in the gradebook.
Read the code carefully and predict exactly what gets printed. Pay attention to which magic method is called in each situation.
Create a Task class with name and priority (an integer, lower = more urgent). Add __lt__ to compare by priority and __repr__ to return 'Task(<name>, priority=<priority>)'. Create three tasks: Task('Email', 3), Task('Bug fix', 1), Task('Meeting', 2). Sort them and print each one.
Create a Formatter class that takes a prefix and suffix in __init__. Define __call__ so that calling the object with a string wraps it with the prefix and suffix. Create Formatter('[', ']') and print the result of calling it with 'hello'. Then create Formatter('<<', '>>') and print the result of calling it with 'warning'.
Refactor this Playlist class to use magic methods instead of regular methods. Replace get_song(index) with __getitem__, song_count() with __len__, has_song(name) with __contains__, and display() with __str__. Then create a playlist with the name 'Rock', add 'Thunder' and 'Highway', and print the playlist, its length, and whether it contains 'Thunder'.