__repr__ and __eq__
The dunder methods that let an object control how it prints for debugging and how it compares for equality with ==.
def __repr__(self) -> str: ...
def __eq__(self, other) -> bool: ...
Purpose
__repr__ and __eq__ are two of Python’s dunder methods (“double underscore”) — the
mechanism by which a custom class participates in built-in behavior. Define __repr__ and
print(obj) (and the REPL, and a debugger) shows something useful instead of a bare memory
address; define __eq__ and == compares by value instead of by identity.
Example
class Point:
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Point(x={self.x}, y={self.y})"
def __eq__(self, other: object) -> bool:
if not isinstance(other, Point):
return NotImplemented
return self.x == other.x and self.y == other.y
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1) # Point(x=1, y=2) — thanks to __repr__
print(p1 == p2) # True — thanks to __eq__, even though p1 is not p2
print(p1 is p2) # False — still two distinct objects
Behavior notes
- Without a custom
__repr__, a class’s default is something like<__main__.Point object at 0x...>— technically correct, practically useless for debugging. - Without a custom
__eq__,==falls back to identity comparison — behaving exactly likeis— which is almost never what a value-like class (a point, a book, a coordinate) actually wants. - Returning
NotImplemented(notFalse) whenotherisn’t a comparable type lets Python fall back to trying the comparison from the other object’s side, rather than asserting “definitely not equal” prematurely. @dataclassgenerates both of these automatically from a class’s fields — writing them by hand, as shown above, is what it does for you.
Common mistakes
- Defining
__eq__without also considering__hash__— Python sets__hash__toNoneautomatically on any class that defines__eq__without also defining__hash__, making instances unhashable (unusable as dict keys or set members) unless__hash__is restored deliberately.@dataclasshandles this trade-off consistently; hand-written classes must decide it themselves. - Comparing
type(other) == Pointinstead ofisinstance(other, Point)in__eq__— the latter correctly handles subclasses; the former silently rejects them.
Implementation notes
Dunder-method dispatch (print() calling __repr__, == calling __eq__) is defined by the
language’s data model — identical across conforming implementations, not a CPython-specific
convention.