Skip to content
datarekha
Python Easy Asked at GoogleAsked at AmazonAsked at Meta

What is the difference between __str__ and __repr__ in Python, and which should you implement first?

The short answer

repr is the developer-facing representation — unambiguous, ideally eval-able back to the object. str is the user-facing string — readable and concise. When only repr is defined, Python falls back to it for str() as well, so implement repr first.

How to think about it

The interviewer wants to know whether you grasp that Python keeps two different representations of an object — show me this for debugging and show me this for a human — and whether you know the one-way fallback between them: str() falls back to repr(), but never the reverse.

HookCalled byAudienceGoal
__repr__repr(), the REPL, !r in f-stringsdevelopersunambiguous; ideally eval(repr(obj)) == obj
__str__str(), print(), plain f-stringsend usersreadable

Put plainly: __repr__ is what you want in a debugger or an error log; __str__ is what you’d put in a UI or a report.

A worked example

A Transaction makes the split concrete — a developer debugging a payment pipeline wants to reconstruct the exact object; a customer reading a receipt just wants the summary:

from datetime import date

class Transaction:
    def __init__(self, amount, currency, on):
        self.amount = amount
        self.currency = currency
        self.on = on

    def __repr__(self):
        return (f"Transaction(amount={self.amount!r}, "
                f"currency={self.currency!r}, on={self.on!r})")

    def __str__(self):
        return f"{self.currency} {self.amount:.2f} on {self.on}"

t = Transaction(49.99, "USD", date(2026, 6, 6))
print("repr:", repr(t))                 # developer-facing
print("str: ", str(t))                  # user-facing
print("f-string default:", f"{t}")      # plain {t} -> __str__
print("f-string with !r:", f"{t!r}")    # {t!r}   -> __repr__

# Inside a collection, Python always uses repr() on each element
print("inside list:", [t, Transaction(20.00, "EUR", date(2026, 6, 7))])
repr: Transaction(amount=49.99, currency='USD', on=datetime.date(2026, 6, 6))
str:  USD 49.99 on 2026-06-06
f-string default: USD 49.99 on 2026-06-06
f-string with !r: Transaction(amount=49.99, currency='USD', on=datetime.date(2026, 6, 6))
inside list: [Transaction(amount=49.99, currency='USD', on=datetime.date(2026, 6, 6)), Transaction(amount=20.0, currency='EUR', on=datetime.date(2026, 6, 7))]

Notice the last line: the list printed each transaction with its __repr__, never its __str__, even though we never asked for repr explicitly. Collections always show their elements’ developer representation.

The fallback chain

When __str__ is missing, Python falls back to __repr__. There is no fallback the other way:

  • Define only __repr__ → both str(obj) and repr(obj) work, with the same output.
  • Define only __str__repr(obj) shows the useless <ClassName object at 0x…>.

That asymmetry is the whole reason for the rule: write __repr__ first, and add __str__ only when a distinct user-facing form is actually worth it.

Learn it properly Dunder Methods

Keep practising

All Python questions

Explore further