Skip to main content

Python Magic Methods: __str__ and __repr__ Explained

When you print a custom Python object without defining how to represent it, you get an unhelpful default: <__main__.Car object at 0x10e8b6d90>. This memory address tells you nothing about the object's data. Magic methods (also called dunder methods, for double-underscore) let you control exactly how your objects appear in print statements and the interactive shell. The two most important are __str__ (user-friendly display) and __repr__ (developer-friendly representation). Mastering these transforms your debugging experience and code professionalism.

When Python encounters operations like printing an object or inspecting it in a shell, it looks for special methods to customize behavior. These methods make your custom classes "play nicely" with built-in Python functions.

Key Takeaways​

  • __str__ returns a user-friendly string representation, used by print() and str()
  • __repr__ returns an unambiguous, developer-focused representation, ideally showing code that could recreate the object
  • If __str__ is absent, Python falls back to __repr__ for print()
  • Always implement __repr__; implement __str__ only when the two representations differ meaningfully

Prerequisites​

Before reading this article, you should be familiar with:

  • Creating basic classes with __init__ methods
  • Instance attributes and how they store object state
  • The concept of return types in methods

What Are Magic Methods and Why Do They Matter?​

Magic methods (or dunder methods, from "double underscore") are special methods that Python calls automatically in response to built-in operations. They let you define custom behavior for otherwise predefined operations.

Examples include:

  • __init__(): Called when creating an instance (constructor)
  • __len__(): Called by len()
  • __add__(): Called by the + operator
  • __str__() and __repr__(): Called when converting an object to string form

These methods enable your custom objects to integrate seamlessly with Python's standard operations. Without them, your objects behave like black boxes; with them, they become first-class citizens in the language.

How Do You Implement __str__() for User-Friendly Display?​

The __str__() method returns a readable, informal string designed for end-users. It's called automatically by print() and the str() constructor. Your goal: make the output informative and pleasant to read.

class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year

def __str__(self) -> str:
"""Returns a user-friendly string representation of the Car."""
return f"A {self.year} {self.make} {self.model}"

my_car = Car("Toyota", "Camry", 2023)

# print() uses __str__
print(my_car)
# Output: A 2023 Toyota Camry

# str() also uses __str__
car_description = str(my_car)
print(car_description)
# Output: A 2023 Toyota Camry

The output is now meaningful: instead of a memory address, users see exactly what the object represents. The __str__() method has full control—you can format it however makes sense for your domain.

Real-world example:

class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance

def __str__(self) -> str:
return f"{self.owner}'s account: ${self.balance:,.2f}"

account = BankAccount("Alice", 5000)
print(account)
# Output: Alice's account: $5,000.00

This is far more useful than <__main__.BankAccount object at 0x...>.

How Do You Implement __repr__() for Developer Debugging?​

The __repr__() method returns an unambiguous, code-like string intended for developers and the Python interpreter. Ideally, it should look like valid Python code that could recreate the object. This representation is used by repr() and also displayed in the interactive shell when you evaluate a variable without printing.

class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year

def __str__(self) -> str:
return f"A {self.year} {self.make} {self.model}"

def __repr__(self) -> str:
"""Returns an unambiguous string representation of the Car."""
return f"Car(make='{self.make}', model='{self.model}', year={self.year})"

my_car = Car("Toyota", "Camry", 2023)

print(repr(my_car))
# Output: Car(make='Toyota', model='Camry', year=2023)

# In interactive shell:
my_car
# Output: Car(make='Toyota', model='Camry', year=2023)

The __repr__() output looks like Python code. A developer can see the class name, all constructor arguments, and their values at a glance. This is invaluable for debugging; you know exactly how to recreate the object.

How Do __str__ and __repr__ Work Together?​

Python's fallback behavior is important:

  1. When you call print(obj), Python looks for __str__()
  2. If __str__() is missing, Python falls back to __repr__()
  3. When you call repr(obj) or inspect in a shell, Python looks only for __repr__()

Key rule: Always implement __repr__(). It's the more fundamental of the two and provides a solid fallback if __str__() is absent.

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

def __repr__(self) -> str:
return f"Book(title='{self.title}')"

my_book = Book("1984")

print(my_book)
# Output: Book(title='1984') ← Uses __repr__ as fallback

repr(my_book)
# Output: Book(title='1984')

When both methods exist, print() uses __str__() and repr() uses __repr__():

class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self) -> str:
return f"{self.name} is {self.age} years old"

def __repr__(self) -> str:
return f"Person(name='{self.name}', age={self.age})"

person = Person("Alice", 30)

print(person)
# Output: Alice is 30 years old ← __str__

repr(person)
# Output: Person(name='Alice', age=30) ← __repr__

This separation makes sense: end-users see the human-friendly __str__() output, while developers see the precise __repr__() form for debugging.

Frequently Asked Questions​

When should I implement both __str__ and __repr__?​

Implement __repr__ always. Implement __str__ only when you want a different, more user-friendly output. If they'd be identical, skip __str__ and rely on the fallback.

What if I want __repr__ to return valid executable code?​

That's the goal, but not always practical. If your object constructor is complex, a readable approximation is fine. The Python philosophy is: make it look like code that could work, even if copying and pasting it won't always succeed.

Can I include newlines or multiple lines in __str__?​

Yes. If your object represents multiple pieces of information, formatting across lines is fine:

def __str__(self) -> str:
return f"""
Name: {self.name}
Age: {self.age}
Email: {self.email}
""".strip()

What's the performance cost of __str__ and __repr__?​

Minimal. They're called only when needed (printing, inspection, logging). Avoid expensive operations inside them (like database queries); keep them lightweight.

Should child classes override __str__ and __repr__?​

Yes, if they add new attributes or change the object's meaning. Call super().__repr__() only if it's helpful; usually, redefine them completely for clarity.

Further Reading​


Challenge: Create a Product class with name, price, and quantity attributes. Implement both __str__() (returning something like "Product: Widget ($9.99, 10 in stock)") and __repr__() (returning "Product(name='Widget', price=9.99, quantity=10)"). Test both print() and repr() to verify they use the right methods.

Next article: We'll explore more magic methods—specifically __init__, __len__, and operators like __add__—to make your classes behave like built-in Python types. Then we'll move into inheritance and polymorphism.

Happy coding!