Python's __repr__ and __str__: Controlling How Objects Print
- The default repr of a custom object shows only its class name and memory address, which tells you almost nothing while debugging.
__repr__is aimed at developers; ideally it looks like valid Python that could recreate the object.__str__is aimed at end users and is whatprint()andstr()use, falling back to__repr__if it's not defined.- Containers like lists and dicts always call
repr()on their elements, neverstr(), even insideprint().
Default repr and why it's not useful
Print an instance of a plain class and Python shows something like this:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(3, 4)
print(p) # <__main__.Point object at 0x7f2a3c0b5d90>
That's the inherited default from object.__repr__: the class name and a memory address. It confirms the object exists and its type, and that's about it — two different Point instances with identical coordinates print as two different, equally uninformative strings. In a debugging session with a dozen objects logged, this default is close to useless.
Defining __repr__ for debugging
Overriding __repr__ fixes this directly. The convention, from the official documentation, is that repr() should ideally return a string that, if fed back into eval(), would recreate an equal object:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x!r}, y={self.y!r})"
p = Point(3, 4)
print(p) # Point(x=3, y=4)
print([p, p]) # [Point(x=3, y=4), Point(x=3, y=4)]
That convention isn't a strict requirement — plenty of reasonable __repr__ implementations aren't literally eval-able, especially for objects wrapping file handles or network connections — but it's a good default to aim for because it forces you to include the fields that actually distinguish one instance from another. The !r conversion in the f-string matters: it calls repr() on self.x rather than str(), so a string field shows its quotes, which stops Point(x=3, y=4) from being ambiguous with Point(x='3', y='4').
Defining __str__ for end users
__str__ is what gets called by print(), str(), and f-string interpolation without !r. It's meant for output a non-developer might actually read:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x!r}, y={self.y!r})"
def __str__(self):
return f"({self.x}, {self.y})"
p = Point(3, 4)
print(p) # (3, 4) -- __str__
print(repr(p)) # Point(x=3, y=4) -- __repr__
Not every class needs both. A data-heavy class used mostly in logs and debugging sessions often only needs __repr__. A class that represents something meant for a user-facing message — a currency amount, a formatted date, a validation error — benefits from a distinct __str__ that drops the internal structure and shows just the meaningful value, in the same spirit as the custom exception classes covered in the custom exceptions guide, where a clear message matters as much as the exception type.
Why str() falls back to __repr__
If a class defines __repr__ but not __str__, calling str() or print() on an instance uses __repr__ instead of falling back to the useless default:
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f"Point({self.x}, {self.y})"
print(Point(1, 2)) # Point(1, 2) -- str() fell back to __repr__
This is why defining just __repr__ is a reasonable minimum for most classes: it immediately improves both debugging output and casual print() calls, with no extra code. Defining __str__ only becomes worth the effort when the developer-facing and user-facing representations genuinely need to differ.
repr inside lists, dicts, and f-strings
A detail that surprises people the first time: printing a list of objects never calls __str__ on the elements, only __repr__, even though the outer print() call itself uses str() on the list:
points = [Point(1, 2), Point(3, 4)]
print(points) # [Point(1, 2), Point(3, 4)] -- reprs, not strs
d = {"origin": Point(0, 0)}
print(d) # {'origin': Point(0, 0)} -- repr again, note the quoted key too
Containers use repr() for their elements because a container's own str() is really its repr() — lists and dicts don't define a separate __str__ at all. This is also why string keys in a printed dict show their quotes, and why it's worth writing a genuinely useful __repr__ even for classes where you assumed only __str__ would ever be seen in practice; nearly every debugging session ends up printing a container full of your objects sooner or later. The Python data model documentation covers the exact contract for both methods in more depth.