Skip to main content

Python Magic Methods: The Complete __dunder__ Guide

Advanced30 min8 exercises120 XP
0/8 exercises

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.

Without magic methods, printing is ugly
Loading editor...

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.

__str__ for users, __repr__ for developers
Loading editor...
__repr__ is used inside lists and containers
Loading editor...

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.

__len__ and __bool__ in action
Loading editor...

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.

Custom equality with __eq__
Loading editor...

__lt__ (less than) is the foundation for sorting. Once you define it, Python can sort a list of your objects using sorted() or .sort().

__lt__ enables sorting
Loading editor...

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.

Making a class indexable and searchable
Loading editor...

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.

Callable objects with __call__
Loading editor...

How Do Magic Methods Work Together in Practice?

Let's build a ShoppingCart class that combines several magic methods into one cohesive object.

A ShoppingCart using multiple magic methods
Loading editor...

Practice Exercises

Add String Representations to a Book
Write Code

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().

Loading editor...
Build a Team with __len__
Write Code

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().

Loading editor...
Fix the Broken Equality Check
Fix the Bug

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.

Loading editor...
Make a Gradebook Indexable
Write Code

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.

Loading editor...
Predict the Magic Method Output
Predict Output

Read the code carefully and predict exactly what gets printed. Pay attention to which magic method is called in each situation.

Loading editor...
Make Tasks Sortable by Priority
Write Code

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.

Loading editor...
Create a Callable Formatter
Write Code

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'.

Loading editor...
Refactor a Playlist with Magic Methods
Refactor

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'.

Loading editor...