SECTION 1
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.
1991 • Python 0.9 Released
__init__ and __repr__ as part of its object model.2001 • New-Style Classes (Python 2.2)
2008 • Python 3.0 Formalizes Protocols
print becomes a function (not a statement), meaning __str__ is now the primary way objects describe themselves to users.2020s • Modern Python & Data Classes
@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.
SECTION 2
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
KEY TAKEAWAY
__str__ is the plug for print(), __repr__ is the plug for the console, and __len__ is the plug for len().SECTION 3
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.
SECTION 4
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.
SECTION 5
__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 |
Key differences between __str__ and __repr__
SECTION 6
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().
Creating a Playlist with Dunder Methods
- 1
Step 1 — Define the class and __init__
Start by creating the class with an__init__method that stores a name and a list of songs.class Playlist: def __init__(self, name, songs): self.name = name self.songs = songsInstance variablesself.nameandself.songsare stored. - 2
Step 2 — Implement __str__
Define__str__to return a friendly message for users.def __str__(self): return f"{self.name} ({len(self.songs)} songs)"Nowprint(p)will show something like "Chill Vibes (12 songs)".print(p)→Chill Vibes (12 songs) - 3
Step 3 — Implement __repr__
Define__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!rformat spec ensures strings are shown with quotes, making the output unambiguous.repr(p)→Playlist('Chill Vibes', ['Song A', 'Song B', ...]) - 4
Step 4 — Implement __len__
Define__len__to return the number of songs.def __len__(self): return len(self.songs)Nowlen(p)returns an integer count of songs in the playlist.len(p)→12 - 5
Step 5 — Test it out
Create an instance and try all three:p = Playlist('Chill Vibes', ['Moonlight', 'Ocean', 'Breeze'])Callingprint(p)uses__str__. Typingpin the console uses__repr__. Andlen(p)returns 3.All three built-in functions now work seamlessly with your custom class.
SECTION 7
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." |
Dunder method strengths vs. common beginner pitfalls
BEST PRACTICE
__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.SECTION 8
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 |
Preview of additional dunder methods you'll encounter next
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.
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
__str__ and __repr__? Who is the intended audience for each, and what happens when you define only __repr__ but not __str__?Show answer
__str__ returns a human-friendly, readable string intended for end users — it's what appears when you call print(). __repr__ returns an unambiguous string intended for developers and debugging — it's what appears in the interactive console. If you define only __repr__, Python automatically falls back to it whenever __str__ is needed. This is why many Python guides recommend always defining __repr__ at minimum.Question 2: BASIC
Book with attributes title and pages. Write the __str__ method so that print(b) where b = Book('1984', 328) outputs 1984 (328 pages).Show answer
def __str__(self): return f"{self.title} ({self.pages} pages)". This uses an f-string to combine the title and page count. When Python calls print(b), it dispatches to b.__str__(), which returns the formatted string. The key requirement is that it returns a str, not an integer or None.Question 3: INTERMEDIATE
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?Show answer
__len__ method should be def __len__(self): return len(self.cards), which returns 52 for a full deck. The __repr__ might be def __repr__(self): return f"Deck({len(self.cards)} cards)". Notice that __len__ delegates to the built-in list's own len(). This is a common pattern: your dunder method wraps the behavior of an internal data structure.Question 4: APPLIED
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)].Show answer
__str__ might return "Shopping Cart: 2 items, Total: $1029.98" — friendly and useful for displaying to a user. __repr__ might return "ShoppingCart([('Laptop', 999.99), ('Mouse', 29.99)])" — showing the exact constructor call a developer could use to recreate it. __len__ returns 2, the number of distinct items. Each method serves a different purpose: user display, developer inspection, and size query.Question 5: CRITICAL THINKING
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.Show answer
__len__ must return a non-negative integer, but self.degrees is a float — Python will raise a TypeError. Second, a Temperature is not a collection of items, so defining __len__ is semantically misleading — it would confuse other developers who expect len() to mean "how many items." A better approach would be to use __float__ (which lets float(t) return the temperature value) or simply access t.degrees directly.SUMMARY
Lesson Summary
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.