Skip to Content
Course content

123: Magic Methods: __str__ and __repr__

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a small project the other day—a basic library catalog—and I hit a wall that every Python developer hits eventually. It’s not a bug that crashes the program, but a "developer experience" bug. Let me show you what I mean.

The "Ugly Object" Problem

I started by defining a simple Book class. I just wanted to keep track of the title and the author. Here is the bare-bones version I wrote:

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

my_book = Book("The Left Hand of Darkness", "Ursula K. Le Guin")
print(my_book)

When I ran this, I expected to see the title or maybe the author. Instead, Python gave me this:

<__main__.Book object at 0x7fb8c1234560>

Technically, Python is being honest. It's telling me this is a Book object located at a specific memory address. But as a human, this is useless. If I have a list of a hundred books and I'm trying to debug why one is missing, staring at hex codes isn't going to help me.

Making it Human-Readable

I remember that Python has these "magic methods" (or dunder methods) that allow us to hook into built-in behavior. To fix the print() output, I need __str__. This method is specifically designed to return a "pretty" string for the end-user.

Let's try adding it:

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def __str__(self):
        return f"'{self.title}' by {self.author}"

my_book = Book("The Left Hand of Darkness", "Ursula K. Le Guin")
print(my_book) # Output: 'The Left Hand of Darkness' by Ursula K. Le Guin

That's much better. Now, when I call print(), Python looks for __str__ and uses that return value. But wait—I noticed something strange. When I looked at the object in the interactive console (the REPL) or put it inside a list, the "ugly" hex code came back.

books = [Book("1984", "George Orwell"), Book("Brave New World", "Aldous Huxley")]
print(books) 
# Output: [<__main__.Book object at 0x...>, <__main__.Book object at 0x...>]

Why did __str__ work for a single object but fail inside the list? It's because print(list) doesn't call __str__ on the items inside; it calls __repr__.

The Developer's Perspective

This is where __repr__ comes in. While __str__ is for the user, __repr__ (short for representation) is for the developer. Its goal is to be unambiguous. Ideally, the string returned by __repr__ should look like the Python code used to create the object.

Let's refine the class again:

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

    def __str__(self):
        return f"'{self.title}' by {self.author}"

    def __repr__(self):
        return f"Book(title='{self.title}', author='{self.author}')"

my_book = Book("The Left Hand of Darkness", "Ursula K. Le Guin")
books = [my_book]

print(my_book) # Still calls __str__: 'The Left Hand of Darkness' by Ursula K. Le Guin
print(books)   # Now calls __repr__: [Book(title='The Left Hand of Darkness', author='Ursula K. Le Guin')]

Now the debugging process is a breeze. I can see exactly what's inside my list without having to loop through it and print each attribute manually.

A Quick Rule of Thumb

You might wonder: "If I only implement __repr__, do I still need __str__?"

Actually, no. If __str__ is missing, Python falls back to __repr__. But the reverse isn't true. If you only have __str__, you'll still get those ugly hex codes in your lists and logs. My personal rule: always implement __repr__ first. It's your safety net for debugging. Add __str__ only if you need a cleaner, more simplified version for non-developers to see.




📋 Practical Task

Exercise: Debugging the E-Commerce Inventory

You are building an inventory system for a store. You have a Product class, but currently, when you print a list of products, it's impossible to tell which is which because they all look like <__main__.Product object at ...>.

Your Task:

  • Implement __repr__ so that it returns a string that looks like the constructor call: Product(name='...', price=...).
  • Implement __str__ so that it returns a user-friendly string: [Item] Name - $Price.
class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    # TODO: Implement __repr__
    
    # TODO: Implement __str__

# Test your code:
p1 = Product("Mechanical Keyboard", 120.00)
p2 = Product("Gaming Mouse", 60.00)
inventory = [p1, p2]

print(f"Single product: {p1}")       # Should use __str__
print(f"Inventory list: {inventory}") # Should use __repr__ for items
Rating
0 0

There are no comments for now.

to be the first to leave a comment.