Dunder Methods (Special Methods)
Hook into print(), len(), and == with dunder methods
In this lesson
Python lets your objects behave like its built-in types by defining special methods, the ones with double underscores, nicknamed ‘dunder’ methods. Define the right ones and print(obj), len(obj), and obj1 == obj2 all just work. This lesson covers the few you’ll actually use.
Explain it like I’m 5
Dunder methods are special hooks Python looks for when it needs your object to act like a normal Python thing, to be printed, measured, or compared.
__str__ and __repr__: readable output
By default, printing an object shows something unhelpful like <__main__.Book object at 0x...>. Define __str__ to control what print() and str() show, a friendly, human-readable form. __repr__ is the developer-facing version, shown in the REPL and in lists, and ideally unambiguous for debugging.
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
b = Book("Python 101", 320)
print(b) # the unhelpful default
<__main__.Book object at 0x10a3f9d50>
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
def __str__(self):
return f"'{self.title}' ({self.pages} pages)"
b = Book("Python 101", 320)
print(b)
'Python 101' (320 pages)
__len__ and __eq__: measure and compare
Define __len__ and Python’s built-in len() will work on your object. Define __eq__ and the == operator compares objects the way you choose (say, two books are equal if their titles match). Add these only when there’s a clear need — not every class wants a length or custom equality.
class Playlist:
def __init__(self, name, songs):
self.name = name
self.songs = songs
def __str__(self):
return f"{self.name}: {len(self.songs)} songs"
def __len__(self):
return len(self.songs)
p = Playlist("Focus", ["Song A", "Song B", "Song C"])
print(p)
print(len(p))
Focus: 3 songs 3
Common mistake: Returning a non-string from __str__
It’s easy to forget the requirement and return a number or a list.
__str__ must return a string. Wrap your value in an f-string or str(...) so the result is always text.
Common mistake: Making __repr__ too vague to help debugging
A throwaway __repr__ that returns just the class name gives no useful detail when you’re hunting a bug.
Aim for a __repr__ that shows the key attributes, ideally looking like the call that would recreate the object, e.g. Book('Python 101', 320).
Common mistake: Adding special methods before there's a use case
Dunder methods feel advanced, so they get added ‘just in case’.
Add a dunder method when something concrete needs it, when you want to print the object, measure it, or compare it. Otherwise leave it out.
Which method controls what print(object) shows?
print() and str() call __str__ for the human-readable form.
What must __str__ return?
__str__ must return a string; returning anything else raises a TypeError.
Why might __repr__ differ from __str__?
__str__ targets end users; __repr__ targets developers and aims to be unambiguous for debugging.
Mini exercise (medium)
Create a Playlist class that stores a name and a list of songs. Add __str__ so printing it shows something like "Road Trip (2 songs)", and __len__ so len(playlist) returns the number of songs.
Have a go. Finish the code and press Run to see the result immediately, right on this page.
class Playlist:
def __init__(self, name, songs):
self.name = name
self.songs = songs
def __str__(self):
# TODO: return e.g. "Road Trip (2 songs)"
return ""
def __len__(self):
# TODO: return how many songs are in the playlist
return 0
p = Playlist("Road Trip", ["Highway", "Sunset"])
print(p)
print(len(p))
__str__ should return f"{self.name} ({len(self.songs)} songs)"; __len__ should return len(self.songs).
class Playlist:
def __init__(self, name, songs):
self.name = name
self.songs = songs
def __str__(self):
return f"{self.name} ({len(self.songs)} songs)"
def __len__(self):
return len(self.songs)
p = Playlist("Road Trip", ["Highway", "Sunset"])
print(p)
print(len(p))
Road Trip (2 songs)
2
assert str(p) == "Road Trip (2 songs)", "__str__ should return 'Road Trip (2 songs)'"
assert len(p) == 2, "__len__ should return the number of songs"
assert isinstance(str(Playlist("X", [])), str), "__str__ must return a string"
print("✓ Feels like a built-in type!")