Historical Context & Motivation
When Python was first designed in the late 1980s, Guido van Rossum wanted a language where objects could interact with built-in functions like print() and len() in a natural, consistent way. Instead of forcing every programmer to invent their own method names like get_length() or to_string(), Python introduced a standardized set of special methods — methods whose names begin and end with double underscores, like __str__ and __len__. These are affectionately called dunder methods (short for "double underscore").
This idea didn't appear out of thin air. Earlier languages like C++ had operator overloading, and Smalltalk let objects respond to any message. Python borrowed these ideas and made them cleaner by giving each special behavior a predictable, recognizable name. Over the decades, the list of dunder methods has grown, but the core philosophy has stayed the same: let custom objects behave just like built-in types.
__init__ and __repr__ as part of its object model.print becomes a function (not a statement), meaning __str__ is now the primary way objects describe themselves to users.@dataclass automatically generate __repr__ and other dunder methods, showing how central they remain in everyday Python.The fundamental question dunder methods answer is this: when you create your own class — say, a Playlist or a Student — how does Python know what to display when you print it, or how to measure its length? Without dunder methods, Python would have no idea. With them, your class speaks the same language as Python's built-in functions.
Core Principles & Definitions
Before diving into individual methods, you need to understand the broader principle at work. Python uses a design philosophy sometimes called the data model (or "protocol" system). When you call a built-in function like len(my_list), Python doesn't hardcode behavior for each type. Instead, it looks for a __len__ method on the object and calls it. This means any class you write can plug into the same system, as long as you define the right dunder method.
__str__ — Human-Friendly Display
print() and str(). Returns a readable, friendly string meant for end users. Think of it as the "nice" description of your object.__repr__ — Developer-Focused Representation
repr(). Returns an unambiguous string, ideally one you could paste back into Python to recreate the object.__len__ — Size / Count
len() function. Must return a non-negative integer. It tells Python how many items your object contains — useful for collections, inventories, or any countable thing.The Hook Pattern
__str__ is the plug for print(), __repr__ is the plug for the console, and __len__ is the plug for len().Visual Explanation — How Python Calls Dunder Methods
The diagram below shows what happens behind the scenes when you use built-in functions on a custom object. Each built-in function acts as a dispatcher that looks up the corresponding dunder method on your object's class. If the method exists, Python calls it; if not, you get a TypeError.
print(obj), Python's dispatcher translates that into a call to obj.__str__(). The same hook pattern applies to repr() and len(). You define the right-hand side; Python handles the left.Notice how the three built-in functions on the left each map to exactly one dunder method on the right. Your class sits at the end of this chain. By filling in those method definitions, you're telling Python: "Here's what my object looks like when printed, how it identifies itself for debugging, and how many items it contains." The dispatcher in the middle is invisible to you — Python handles it automatically.
How Each Method Works Under the Hood
__str__ — The User-Facing String
When you write print(my_playlist), Python internally calls my_playlist.__str__(). This method must return a string. It should be clean, readable, and meant for humans. For example, a Playlist might return "Chill Vibes (12 songs)". If you don't define __str__, Python falls back to __repr__ instead.
__repr__ — The Developer-Facing Representation
When you type a variable name in the Python interactive shell and press Enter, Python calls __repr__. This method should return an unambiguous string that ideally looks like valid Python code. For our Playlist, that might be "Playlist('Chill Vibes', 12)". The goal is to give a developer enough information to recreate or debug the object. A good rule of thumb: __repr__ is for developers; __str__ is for end users.
__len__ — The Size Reporter
When you call len(my_playlist), Python calls my_playlist.__len__(). This method must return a non-negative integer. It makes sense for any class that holds a collection of items — a Playlist has songs, a Classroom has students, a ShoppingCart has products. If your class doesn't represent a collection, you simply don't define __len__, and calling len() on it will raise a TypeError.
The Fallback Chain
An important detail: if __str__ is not defined, Python falls back to __repr__. This means if you only define one, define __repr__ — it will cover both cases. However, if neither is defined, Python uses the default inherited from the object base class, which produces something unhelpful like <__main__.Playlist object at 0x7f3a>. That's why defining your own dunder methods is so valuable.
__str__ vs __repr__ — A Side-by-Side Comparison
One of the trickiest parts for beginners is understanding why Python has both __str__ and __repr__. The diagram below places them side by side using a real-world example — a Student class — so you can see how the same object produces two different strings for two different audiences.
__str__ output (clean, user-friendly), while the right column shows __repr__ output (detailed, developer-friendly). Both describe the same Student object, just for different audiences.| Feature | __str__ | __repr__ |
|---|---|---|
| Called by | print(), str(), f-strings | repr(), interactive console, inside containers |
| Audience | End users | Developers |
| Must return | A string | A string |
| Ideal style | Readable, pretty | Unambiguous, looks like code |
| Fallback if missing | Falls back to __repr__ | Uses default from object |
Worked Example — Building a Playlist Class
Let's build a Playlist class step by step, implementing all three dunder methods. Imagine you're building a music app and you need your Playlist objects to work smoothly with print() and len().
__init__ method that stores a name and a list of songs. class Playlist:
def __init__(self, name, songs):
self.name = name
self.songs = songsself.name and self.songs are stored.__str__ to return a friendly message for users. def __str__(self):
return f"{self.name} ({len(self.songs)} songs)" Now print(p) will show something like "Chill Vibes (12 songs)".print(p) → Chill Vibes (12 songs)__repr__ to return a string that looks like a valid constructor call. def __repr__(self):
return f"Playlist({self.name!r}, {self.songs!r})" The !r format spec ensures strings are shown with quotes, making the output unambiguous.repr(p) → Playlist('Chill Vibes', ['Song A', 'Song B', ...])__len__ to return the number of songs. def __len__(self):
return len(self.songs) Now len(p) returns an integer count of songs in the playlist.len(p) → 12p = Playlist('Chill Vibes', ['Moonlight', 'Ocean', 'Breeze']) Calling print(p) uses __str__. Typing p in the console uses __repr__. And len(p) returns 3.Strengths, Pitfalls, and Best Practices
Defining dunder methods makes your code more Pythonic and easier to use, but there are a few things to watch out for. The table below highlights common strengths of using these methods alongside pitfalls that beginners often encounter.
| Strength ✅ | Common Pitfall ⚠️ |
|---|---|
print() works instantly with your objects — no extra code at the call site. | Forgetting to return a string from __str__ or __repr__ — returning None or an int causes a TypeError. |
Debugging is faster because __repr__ shows meaningful info instead of a memory address. | Writing the same output for both __str__ and __repr__ — they serve different audiences, so keep them distinct. |
len() gives your collections a standard size-check interface. | Returning a float or negative number from __len__ — it must be a non-negative integer. |
| Your class integrates with the rest of Python's ecosystem (f-strings, logging, containers). | Defining __len__ on a class that doesn't represent a collection — a single Person shouldn't have a "length." |
__str__), "What are your exact qualifications?" (__repr__), "How big is your portfolio?" (__len__). If you've prepared answers, the interview goes smoothly. If not, you get a generic, unhelpful response.Connection to Advanced Dunder Methods
The three methods you've learned — __str__, __repr__, and __len__ — are just the tip of the iceberg. Python has dozens of dunder methods that let you customize nearly every aspect of how your objects behave. The table below previews some of the most commonly used ones that you'll encounter as you continue learning.
| Dunder Method | Triggered By | What It Does |
|---|---|---|
__eq__(self, other) | a == b | Defines how to check equality between two objects |
__lt__(self, other) | a < b | Defines less-than comparison, enabling sorting |
__add__(self, other) | a + b | Defines how to add two objects together |
__getitem__(self, key) | a[key] | Defines indexing/bracket access on your object |
__iter__(self) | for x in a: | Makes your object iterable in for-loops |
Notice the same pattern in every row: a Python operator or built-in function on the left, and a dunder method that "hooks" into it on the right. Once you're comfortable with __str__, __repr__, and __len__, the rest will feel familiar — you're always just telling Python "here's what to do when someone uses this feature on my object." Mastering the three introductory dunder methods gives you the mental framework for all the others.
Practice Problems
__str__ and __repr__? Who is the intended audience for each, and what happens when you define only __repr__ but not __str__?Book with attributes title and pages. Write the __str__ method so that print(b) where b = Book('1984', 328) outputs 1984 (328 pages).Deck represents a deck of playing cards. It stores its cards in a list attribute called self.cards. Write both __len__ and __repr__ for this class. What should len(d) return for a full 52-card deck?ShoppingCart class that stores items as a list of tuples (name, price). Design all three dunder methods (__str__, __repr__, __len__). Describe what each would return for a cart with items [('Laptop', 999.99), ('Mouse', 29.99)].Temperature class with a single attribute self.degrees (a float). They implement __len__ to return self.degrees. Identify two things wrong with this design. Then suggest which dunder method would be more appropriate for representing the temperature value as a number.Lesson Summary
Dunder methods (double-underscore methods) are Python's way of letting your custom classes integrate seamlessly with built-in functions. The three introductory dunder methods are __str__ (returns a human-friendly string for print()), __repr__ (returns an unambiguous, developer-focused string for the console), and __len__ (returns a non-negative integer for len()). Each one follows the same hook pattern: Python's built-in function dispatches to the corresponding dunder method on your class.
Remember that __str__ falls back to __repr__ if not defined, so always define __repr__ at minimum. Only define __len__ when your class genuinely represents a collection of items. These three methods form the conceptual foundation for all of Python's special methods — once you understand the hook pattern, learning additional dunder methods like __eq__, __add__, and __iter__ becomes straightforward.