Back to Blog
Python

Python repr: The __repr__ Method Explained

python **repr**: Learn how to implement Python's __repr__ method to produce unambiguous, developer-friendly object representations for debugging and logging.

Python__repr__debuggingobject representationdunder methods
Illustration contrasting a precise, unambiguous __repr__ string with a human-readable __str__ string for a Python object

python repr requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When you call repr() on a Python object, you get a string that is meant to be unambiguous for developers. The __repr__ method defines that string, and getting it right improves debugging, logging, and error messages across your codebase. This article explains the contract behind python repr, how to implement __repr__ effectively, and where it differs from __str__.

What __repr__ Is and Why It Matters

Every Python class inherits a default __repr__ from object, which produces something like <__main__.MyClass object at 0x7f8b1c3d4a90>. That output tells you almost nothing about the object's state. When you are debugging a failed test or inspecting a log entry, you need to know which instance failed and what data it held. A well-written __repr__ gives you that information immediately.

The built-in repr() function calls __repr__ internally. It is also what the interactive interpreter displays when you type an expression without print(). For example, if you define a class Point and then type Point(3, 4) in a REPL, you see the result of __repr__. This makes __repr__ the primary way developers inspect objects during development.

The Contract of __repr__: Unambiguous Over Readable

The official documentation states that __repr__ should return a string that is "unambiguous" and, if possible, a valid Python expression that recreates the object. In practice, this means the string should contain enough information to identify the object's state. It does not need to be human-friendly; it needs to be precise.

For example, a Point class with x and y attributes might have:

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})"

The !r conversion uses repr() on each attribute, ensuring that strings are quoted and other types are represented accurately. This makes the output Point(x=3, y=4) for integers, or Point(x='a', y='b') for strings. The expression can be copied back into code to recreate the object, which is a useful property for debugging and serialization.

__repr__ vs __str__: When to Use Which

__str__ is meant for human-readable output, while __repr__ is meant for developer-facing, unambiguous output. The str() built-in and print() call __str__; the interactive interpreter and repr() call __repr__. If you only define one, Python falls back to using __repr__ for both, which is often acceptable but not ideal.

Consider a Date class. A human might want "2024-03-14" from str(), but a developer debugging might prefer Date(2024, 3, 14) from repr(). The following table summarizes the difference:

Aspect__repr____str__
Primary purposeUnambiguous developer outputReadable end-user output
Called byrepr(), REPL, f-string !rstr(), print(), f-string
Ideal resultValid Python expressionPlain, human-friendly text
Default fallbackInherited from objectUses __repr__ if not defined

In most cases, you should implement both. If you only implement one, make it __repr__ because it also serves as a fallback for __str__ and is more useful for debugging.

Implementing __repr__ for Custom Classes

A good __repr__ should be concise but complete. It should include the class name and the attributes that define the object's identity. For simple classes, returning a string that looks like a constructor call is a solid approach.

class InventoryItem: def __init__(self, name, unit_price, quantity=0): self.name = name self.unit_price = unit_price self.quantity = quantity def __repr__(self): return ( f"InventoryItem(name={self.name!r}, " f"unit_price={self.unit_price!r}, " f"quantity={self.quantity!r})" )

This output includes every attribute, so you can see the full state at a glance. If an attribute is expensive to compute or not essential, you can omit it, but the representation should still be unambiguous. For example, a cached value might be excluded because it can be recomputed.

When your class inherits from another, include the parent's __repr__ or call super().__repr__() if the parent has a meaningful implementation. Otherwise, you risk losing important context.

Common Mistakes When Writing __repr__

One frequent mistake is returning None or a non-string value. __repr__ must return a string; if it returns None, Python raises a TypeError when repr() is called. Always return a string literal or an f-string.

Another mistake is using str() instead of repr() for attribute values. If you write f"Point(x={self.x}, y={self.y})" and x is a string, the output will be Point(x=hello, y=4), which is ambiguous because hello could be a variable name. Use !r to get quotes and proper escaping.

A third mistake is making __repr__ too verbose. Including every internal detail can clutter logs and make the output harder to read. Stick to the essential state. If the object has a large list or dictionary, consider truncating it or showing only the length, as long as the representation remains unambiguous.

Finally, do not let __repr__ have side effects. It is called implicitly by the debugger, logging frameworks, and error formatters. If it performs I/O or modifies state, it can cause subtle bugs and performance issues.

How repr() Interacts with Collections and Logging

When you put objects in a list or dictionary, their repr() is used when you print the collection. For example:

points = [Point(1, 2), Point(3, 4)] print(points)

This outputs [Point(x=1, y=2), Point(x=3, y=4)] if your __repr__ is defined correctly. Without it, you get <__main__.Point object at 0x...>, which is useless for debugging.

Logging frameworks also use repr() when formatting messages with %r or when you pass objects directly. In the logging module, if you use logger.debug("Point: %r", point), the __repr__ output is what gets written. This makes __repr__ essential for production troubleshooting.

F-strings also support !r to invoke repr() explicitly:

print(f"{point!r}")

This is often more convenient than calling repr(point) and keeps the output consistent.

Performance and Maintainability Considerations

__repr__ is called far more often than you might expect. Every time an object appears in a traceback, a debugger inspection, or a log message, Python invokes it. Therefore, it should be fast and avoid expensive operations like database queries or network calls. If computing a full representation is costly, consider caching the result or using a simpler representation that still identifies the object.

Maintainability also matters. Keep __repr__ in sync with the class attributes. If you add a new attribute that defines the object's state, update __repr__ to include it. Otherwise, debugging output becomes misleading. A common pattern is to use a helper that formats the constructor arguments, reducing duplication.

For classes with many attributes, you can generate the representation dynamically using vars(self):

def __repr__(self): args = ', '.join(f"{k}={v!r}" for k, v in vars(self).items()) return f"{type(self).__name__}({args})"

This approach automatically includes all instance attributes and stays correct as the class evolves. However, it may not match the constructor signature if the class uses property setters or computed fields. Use it when the constructor accepts the same names as the instance attributes.

Finally, be aware that __repr__ is inherited. If you subclass a class with a good __repr__, you may need to override it to include subclass-specific state. Failing to do so can hide important information during debugging.

By following these guidelines, you ensure that python repr works for you instead of against you, turning every object into a clear, self-describing value that speeds up development and reduces time spent digging through logs.

python **repr**: Practical Usage and Code Examples | RYUSLOG DEV