Python • Object-Oriented Programming

Special Methods (Dunder Methods) — Use special methods (__str__, __repr__, __len__) conceptually (intro)

Teach your custom Python objects how to describe themselves, debug cleanly, and report their size.

Code braces with terminal and markup symbols representing Python programming.
Inside this lesson 10 sections 5 questions 5 figures

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

Guido van Rossum publishes the first version of Python, which already includes special methods like __init__ and __repr__ as part of its object model.

2001 • New-Style Classes (Python 2.2)

Python unifies types and classes, making dunder methods even more important. Now all objects — including ints and strings — share the same protocol.

2008 • Python 3.0 Formalizes Protocols

Python 3 cleans up the special method system. print becomes a function (not a statement), meaning __str__ is now the primary way objects describe themselves to users.

2020s • Modern Python & Data Classes

Tools like @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.

1

__str__ — Human-Friendly Display

Called by print() and str(). Returns a readable, friendly string meant for end users. Think of it as the "nice" description of your object.
2

__repr__ — Developer-Focused Representation

Called when you inspect an object in the interactive console or use repr(). Returns an unambiguous string, ideally one you could paste back into Python to recreate the object.
3

__len__ — Size / Count

Called by the built-in 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.
4

The Hook Pattern

All dunder methods follow the same pattern: a built-in function or operator "hooks" into your class by calling a specially named method. You define the method; Python calls it automatically at the right moment.
✦

KEY TAKEAWAY

Think of dunder methods like adapters on a power strip. The power strip (Python) has standard-shaped outlets. If your device (your class) has the right-shaped plug (the dunder method), it connects instantly — no extra wiring needed. __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.

How Built-in Functions Hook into Your Classprint(obj)or str(obj)repr(obj)or console displaylen(obj)built-in functionPython DispatcherLooks up dundermethod on the classobj.__str__()obj.__repr__()obj.__len__()Your Classclass Playlist:def __str__(self):→ friendly textdef __repr__(self):→ debug textdef __len__(self):→ integer countYou define these!Built-in functions → Python dispatcher → Your dunder method → Result
When you call 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__ vs __repr__ — Same Object, Different Outputs__str__"Human-friendly display"Triggered by:print(s) / str(s)Audience:👤 End users / non-codersExample output:"Alex (Grade 11)"Goal:Be readable and clean__repr__"Developer-focused representation"Triggered by:repr(s) / consoleAudience:🛠️ Developers / debuggersExample output:"Student('Alex', 11, 3.8)"Goal:Be unambiguous and reproducibleIf __str__ is missing, Python falls back to __repr__.
The left column shows __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 byprint(), str(), f-stringsrepr(), interactive console, inside containers
AudienceEnd usersDevelopers
Must returnA stringA string
Ideal styleReadable, prettyUnambiguous, looks like code
Fallback if missingFalls 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. 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 = songs
    Instance variables self.name and self.songs are stored.
  2. 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)" Now print(p) will show something like "Chill Vibes (12 songs)".
    print(p) → Chill Vibes (12 songs)
  3. 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 !r format spec ensures strings are shown with quotes, making the output unambiguous.
    repr(p) → Playlist('Chill Vibes', ['Song A', 'Song B', ...])
  4. 4

    Step 4 — Implement __len__

    Define __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) → 12
  5. 5

    Step 5 — Test it out

    Create an instance and try all three: p = Playlist('Chill Vibes', ['Moonlight', 'Ocean', 'Breeze']) Calling print(p) uses __str__. Typing p in the console uses __repr__. And len(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

Think of dunder methods like a job interview checklist. The interviewer (Python) asks specific questions: "How do you describe yourself?" (__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 MethodTriggered ByWhat It Does
__eq__(self, other)a == bDefines how to check equality between two objects
__lt__(self, other)a < bDefines less-than comparison, enabling sorting
__add__(self, other)a + bDefines 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

Explain in your own words: what is the difference between __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

You have a class 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
You would define 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

A class 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
The __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

You're building a 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

A classmate defines a 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
Two problems: First, __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.

Varsity Tutors • Python • Special Methods (Dunder Methods)