repr() in Python 2026: Official String Representation + Modern Debugging & Serialization Use Cases
The built-in repr(obj) returns the official string representation of an object, meant for developers rather than end users. When you inspect a value in the REPL, print a list, read a log line, or look at a failing test, you are usually looking at repr() output. This guide explains how it differs from str(), how to write a good __repr__, the f-string shortcuts, how to keep huge or recursive objects readable, and why repr() is not a serialization format.
All examples run unchanged on Python 3.13.
TL;DR: Key Takeaways
repr(obj)callstype(obj).__repr__(obj)and must return astrstr()is for readers;repr()is for developers, and it should be unambiguous- Where practical, make
eval(repr(obj)) == objtrue; otherwise use the<ClassName ...>form - Containers such as lists and dicts always show their items with
repr() - In f-strings,
{x!r}appliesrepr()and{x=}prints the name together with the repr - Use
reprlibfor very large or self-referencing objects - Use
jsonorast.literal_evalto store and load data, nevereval()on untrusted text
1. repr() vs str(): Same Object, Two Audiences
str() gives a friendly, readable form. repr() shows exactly what the value is: quotes, escape sequences and all. That precision is why debuggers and the interactive prompt use it.
text = "Hello\tPyInns\n"
print(str(text))
print(repr(text))
print(repr(3.0), repr([1, "two", None]))
Output:
Hello PyInns
'Hello\tPyInns\n'
3.0 [1, 'two', None]
The str() version hides the tab and the trailing newline. The repr() version shows them, which is usually what you need when you are chasing a whitespace bug.
2. Writing a Good __repr__ for Your Own Classes
Without __repr__, your objects look like <__main__.Point object at 0x7f...>, which tells you almost nothing. A good repr looks like the code that would rebuild the object:
class Point:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def __repr__(self) -> str:
return f"{type(self).__name__}(x={self.x!r}, y={self.y!r})"
def __str__(self) -> str:
return f"({self.x}, {self.y})"
p = Point(1.5, -2)
print(repr(p)) # Point(x=1.5, y=-2)
print(str(p)) # (1.5, -2)
print([p]) # containers use repr: [Point(x=1.5, y=-2)]
print(eval(repr(p)).x) # 1.5 -- round-trips because the repr is valid Python
Two habits are worth copying from this example:
- Use
!ron each field so strings keep their quotes.Point(x='1')andPoint(x=1)are different objects. - Use
type(self).__name__rather than a hard-coded class name, so subclasses show their own name.
If the object cannot be rebuilt from a short expression (an open file, a database connection, a thread), follow the convention of the standard library and return something like <Connection host='db1' open=True>.
3. f-string Shortcuts: !r and the = Specifier
user = "ada"
retries = 3
path = "C:\\temp\\new"
print(f"{user!r} has {retries} retries") # 'ada' has 3 retries
print(f"{user=}, {retries=}") # user='ada', retries=3
print(f"{path=}") # path='C:\\temp\\new'
print(f"{path=!s}") # path=C:\temp\new
{name=} (available since Python 3.8) is one of the fastest debugging tools in the language. It uses repr() by default, and you can switch to str() with {name=!s}.
4. Dataclasses Generate repr() for You
@dataclass writes a __repr__ from the field list. Mark sensitive fields with repr=False so tokens and passwords never end up in logs or tracebacks.
from dataclasses import dataclass, field
@dataclass
class Order:
order_id: int
items: list[str] = field(default_factory=list)
api_token: str = field(default="", repr=False) # hide secrets from repr
o = Order(42, ["book", "pen"], api_token="s3cr3t")
print(o) # Order(order_id=42, items=['book', 'pen'])
5. Keeping Huge or Recursive Objects Readable with reprlib
Logging the full repr of a 10,000-item list floods your logs. reprlib.repr() shortens the output with sensible limits, and a reprlib.Repr instance lets you set your own:
import reprlib
big = list(range(10_000))
print(reprlib.repr(big)) # [0, 1, 2, 3, 4, 5, ...]
short = reprlib.Repr(maxlist=3, maxstring=12)
print(short.repr(["alpha", "beta", "gamma", "delta"]))
print(short.repr("a very long log message"))
Output:
[0, 1, 2, 3, 4, 5, ...]
['alpha', 'beta', 'gamma', ...]
'a v...sage'
Objects that contain themselves would recurse forever in a hand-written __repr__. The @reprlib.recursive_repr() decorator replaces the repeated reference with ...:
from reprlib import recursive_repr
class Node:
def __init__(self, name: str) -> None:
self.name = name
self.children: list["Node"] = []
@recursive_repr()
def __repr__(self) -> str:
return f"Node({self.name!r}, children={self.children!r})"
root = Node("root")
root.children.append(root) # a cycle
print(repr(root)) # Node('root', children=[...])
6. repr() Is Not Serialization
For built-in literals, repr() output is valid Python, so it can be tempting to save data with repr() and load it back with eval(). Don't. eval() runs arbitrary code. Use ast.literal_eval() when you need to read Python literals, and use json (or a format such as TOML) when you exchange data with other programs:
import ast
import json
settings = {"debug": True, "ratio": 0.25, "tags": ("a", "b")}
as_repr = repr(settings)
print(as_repr)
print(ast.literal_eval(as_repr) == settings) # True: safe for literals only
print(json.dumps({"debug": True, "ratio": 0.25, "tags": ["a", "b"]}))
Output:
{'debug': True, 'ratio': 0.25, 'tags': ('a', 'b')}
True
{"debug": true, "ratio": 0.25, "tags": ["a", "b"]}
7. Best Practices Checklist
- Give every class you debug often a
__repr__. Add__str__only if users need a different, friendlier form. - Make the repr unambiguous and short, and include the fields that identify the object.
- Never include secrets. Use
field(repr=False)in dataclasses or leave them out by hand. - Keep
__repr__cheap and free of side effects. Debuggers call it often, sometimes in the middle of an error. - Log with
%ror{value!r}when the exact value matters.
Conclusion
repr() is a small function that does a lot of work every time you debug. Write clear __repr__ methods, use {x=} and !r in f-strings, and use reprlib for big structures. Keep repr() for people reading your output, and use json or ast.literal_eval for data.