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 byprint()andstr()__repr__returns an unambiguous, developer-focused representation, ideally showing code that could recreate the object- If
__str__is absent, Python falls back to__repr__forprint() - 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 bylen()__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:
- When you call
print(obj), Python looks for__str__() - If
__str__()is missing, Python falls back to__repr__() - 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
- Official Python Data Model:
__str__and__repr__ - Real Python: Python
__repr__vs__str__ - PEP 20: The Zen of Python
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!