LESSON 63 · DEEPER PYTHON32 min

Make Objects Work with repr, ==, and len

Implement three special methods with clear value semantics and handle unsupported comparisons correctly.

Before you start: Classes and Objects · Conditions · Functions

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: Make Objects Work with repr, ==, and len

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    Python already knows how to print representations, compare values, and ask for a length. But a new class does not automatically know what those operations should mean for its own data. Two Book objects can carry the same title and page count and still compare unequal by default because they are separate objects. Calling len on one raises TypeError unless we define the length protocol. A default representation also includes identity details that are not very helpful when a test fails. We will use three special methods to give this class a clear value meaning: `__repr__` for an informative representation, `__eq__` for equality by title and pages, and `__len__` for page count.

  2. 00:44

    We will run the defaults, deliberately return a bad length, diagnose Python's error, then implement and verify the complete class. On the companion page, the browser task asks you to write these methods and check edge cases. Before typing, decide whether a zero-page draft is a valid Book and whether a negative page count should be allowed to exist. The two books hold matching field values, but they are not the same object, so default comparison reports False. That default is not wrong for every class. It simply does not fit our desired value semantics. Then len asks the object for `__len__`, which the class does not provide, so Python raises TypeError.

  3. 01:27

    We should not add a special method only to silence the exception; we need a precise meaning for it. Here len will be the number of pages. The representation should make both values visible with a quoted title so punctuation in a title does not blur the string boundary. Equality should compare those same two value fields. A different application might use an ISBN as its equality key, but that would be a different contract. Decide the contract before coding. Also, avoid testing the exact default repr, since it can include a memory address that changes on each run. Test the observable behavior that matters instead: what our implementation returns after we define it.

  4. 02:11

    We added a `__len__` method, but constructing a book with negative two pages created an invalid object. When len calls the method, Python refuses the negative answer and raises ValueError. The better place to prevent this state is the constructor. It should require a nonnegative integer page count before storing it. In this lesson, a boolean is not a page count even though `bool` is an `int` subclass in Python, so an exact `type(pages) is int` check is appropriate. Zero pages can represent a draft and should return zero. A float such as 2.5 should be rejected rather than truncated.

  5. 02:51

    Notice how the class invariant makes the special method simple: if construction guarantees valid pages, `__len__` can just return them. We will also give equality a type check. Returning NotImplemented for a foreign type lets Python try the other operand's comparison instead of forcing a premature Boolean. It is a dispatch signal, not the same thing as raising NotImplementedError. The constructor prevents invalid page counts before any Book exists. The representation uses `!r` for the title, so Python chooses quoting and escaping that reveal the exact string. The equality method first asks whether the other operand is a Book.

  6. 03:34

    If not, it returns NotImplemented and lets Python's comparison machinery continue. If it is a Book, we compare the title and page count as a pair. The length method returns the already validated page count. These three methods now express separate concepts: display, value identity, and size. Do not implement equality by comparing repr strings, because a later formatting edit could silently change which objects compare equal. One more consequence matters: this Book is mutable and defines value equality, so Python normally makes it unhashable. That is sensible because changing its title or pages after inserting it as a dictionary key would break lookup assumptions.

  7. 04:19

    Do not add a hash method just to make it fit into a set; consider an immutable representation if key use is truly needed. Zero pages are allowed, so len returns zero. Changing one value field makes two books unequal. Calling `__eq__` directly with a foreign object reveals NotImplemented, which is what the comparison protocol expects for an unsupported operand. The representation of a title containing an apostrophe remains unambiguous because `!r` chooses suitable quotes. Finally, negative, boolean, and fractional page counts all fail during construction. These tests cover the decisions we made, not every possible future feature of a book catalog.

  8. 05:03

    If a check fails in the browser task, separate the three methods and verify one behavior at a time. Ask whether the constructor created valid state, whether repr quotes the title, whether equality compares fields rather than identities, and whether len returns a nonnegative integer. Once these operations have a stable meaning, Book objects become easier to inspect and use in ordinary Python code. Special methods are powerful precisely because they connect a small class to syntax people already know; use that power to express a contract, not to hide a surprise.

Understand the concept

Python's built-in operations call special methods on objects. repr(book) asks for __repr__, `book == other` uses __eq__, and len(book) uses __len__. These methods let a user-defined class participate in ordinary Python syntax. They should describe coherent behavior, not just silence a TypeError. A Book in this lesson stores a title and a nonnegative page count; len(book) means the number of pages.

A useful __repr__ returns an unambiguous string such as `Book('Python', 120)`. The `!r` conversion quotes strings and escapes special characters, making the result more informative than plain string interpolation. An __eq__ method should compare the fields that define value equality. If the other object is not a Book, return NotImplemented rather than False. This gives the other operand a chance to implement the comparison; if neither side handles it, `==` resolves to False.

The __len__ method must return a nonnegative integer that fits Python's index-sized length: at most sys.maxsize. Validate pages in __init__ instead of letting a negative or oversized value survive until someone calls len. Python treats bool as an int for some checks, so use `type(pages) is int` if a boolean does not make sense as a page count. If a class is mutable and defines __eq__, Python generally makes it unhashable unless a compatible __hash__ is provided. This lesson does not add a hash method; two mutable equal books should not be inserted as dictionary keys by accident.

The checked task asks for all three methods. It compares equal and unequal books, checks repr for quoted titles, and verifies that len follows the stored pages. Keep the methods short and make the constructor's data contract explicit. Do not implement equality by comparing repr strings: representations are for readers, while equality expresses the intended value rule.

  • repr calls __repr__
  • == dispatches to __eq__ and may use NotImplemented
  • len requires a nonnegative integer from __len__
  • Mutable value equality and hashing need a deliberate policy

See it step by step

Read the code, predict the output, then compare it with the result.

01. Make a representation that shows quoted data

PYTHON
class Label:
    def __init__(self, text):
        self.text = text

    def __repr__(self):
        return f"Label({self.text!r})"

print(repr(Label("Ada's note")))
EXPECTED OUTPUT
Label("Ada's note")

!r uses repr on the string, preserving its boundaries and quotes.

02. Compare values instead of object identities

PYTHON
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __eq__(self, other):
        if not isinstance(other, Point):
            return NotImplemented
        return (self.x, self.y) == (other.x, other.y)

print(Point(2, 3) == Point(2, 3))
print(Point(2, 3) == (2, 3))
EXPECTED OUTPUT
True
False

Equal coordinates count as equal Points; a tuple is left to the normal comparison fallback.

03. Give len a precise meaning

PYTHON
class Chapter:
    def __init__(self, paragraphs):
        self.paragraphs = list(paragraphs)

    def __len__(self):
        return len(self.paragraphs)

chapter = Chapter(["Intro", "Example", "Summary"])
print(len(chapter))
EXPECTED OUTPUT
3

len(chapter) delegates to __len__, which returns a nonnegative integer count.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

See what Python does before a class defines protocols

Without __repr__, Python normally shows a type and memory-related identity that varies between runs. Without __eq__, two separately created objects with the same fields usually compare unequal. Without __len__, len(book) raises TypeError. Those are reasonable defaults for arbitrary objects, but our Book has a value meaning and an explicit page count.

Do not guess the exact default repr text in an automated example because its address is unstable. Instead, compare identity and show the length error name. Then decide the class contract: title and pages define equality, repr exposes both fields for diagnosis, and len counts pages. Each special method should answer one well-defined question.

PYTHON
class Book:
    def __init__(self, title, pages):
        self.title, self.pages = title, pages

first = Book("Python", 120)
second = Book("Python", 120)
print(first == second)
try:
    print(len(first))
except TypeError as error:
    print(type(error).__name__)
EXPECTED OUTPUT
False
TypeError
Follow the reasoning
  1. Create two distinct objects with identical fields.
  2. Notice that default identity equality and missing length do not express the planned Book contract.
02 / 03

Implement readable representation and value equality

The `!r` conversion quotes a title and escapes its special characters, so a representation such as Book('Python', 120) shows where the string starts and ends. This is especially useful in a list of books or an assertion failure. A repr need not be evaluable in every class, but this small one can be concise and unambiguous.

For equality, compare the fields defining a Book value. Return NotImplemented when the other operand is not a Book. It is a sentinel that asks Python to try the other operand's reflected comparison, not a Boolean and not an exception. Do not base equality on a display string; changing formatting should not change whether two books are equal.

PYTHON
class Book:
    def __init__(self, title, pages):
        self.title, self.pages = title, pages

    def __repr__(self):
        return f"Book({self.title!r}, {self.pages})"

    def __eq__(self, other):
        if not isinstance(other, Book):
            return NotImplemented
        return (self.title, self.pages) == (other.title, other.pages)

print(repr(Book("Ada's notes", 8)))
print(Book("Python", 120) == Book("Python", 120))
print(Book("Python", 120) == ("Python", 120))
EXPECTED OUTPUT
Book("Ada's notes", 8)
True
False
Follow the reasoning
  1. Inspect the quoted title in repr output.
  2. Compare matching books and a foreign tuple to see the equality fallback.
03 / 03

Give len a safe meaning and consider hashing

len calls __len__ and expects a nonnegative integer no larger than sys.maxsize; a bigger Python int raises OverflowError at the len call. Validate page counts when the object is constructed so every later length call is sound. A zero-page draft is permitted, while a negative, oversized, float, or boolean count is rejected. The example uses `type(pages) is int` because booleans are integer subclasses yet nonsensical as page counts here.

Defining value equality on a mutable class changes its relationship to hashing. Python makes such instances unhashable unless a compatible __hash__ is defined. That protects sets and dictionaries from a key changing its equality fields while stored. Do not add a hash method merely to silence an error; decide whether the value should be immutable before making it a key.

PYTHON
import sys

class Book:
    def __init__(self, title, pages):
        if type(pages) is not int or pages < 0 or pages > sys.maxsize:
            raise ValueError("pages must be nonnegative int")
        self.title, self.pages = title, pages

    def __eq__(self, other):
        if not isinstance(other, Book):
            return NotImplemented
        return (self.title, self.pages) == (other.title, other.pages)

    def __len__(self):
        return self.pages

print(len(Book("Draft", 0)))
print(len(Book("Python", 120)))
try:
    Book("Wrong", -1)
except ValueError as error:
    print(error)
EXPECTED OUTPUT
0
120
pages must be nonnegative int
Follow the reasoning
  1. Verify the allowed zero and positive lengths.
  2. Reject negative and oversized counts before an invalid Book exists.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnComplete Book.__repr__ as `Book(<quoted title>, <pages>)` using `self.title!r`; __eq__ should compare title and pages for another Book and return NotImplemented for other types; __len__ should return pages. The supplied constructor rejects page counts above sys.maxsize so len(book) remains valid. The prepared output is Book('Python', 120), True, and 120.
Need a hint?

Use `return f'Book({self.title!r}, {self.pages})'`, then `if not isinstance(other, Book): return NotImplemented`, followed by a field comparison. __len__ returns `self.pages`.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.Which method supplies repr(book)?

    02.What should __eq__ return for an unsupported other type?

    03.What result is valid from __len__?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Returning False for every foreign object

    COMMON MISTAKE
    if not isinstance(other, Book): return False
    FIX
    if not isinstance(other, Book): return NotImplemented

    What happens: The other operand cannot try its reflected equality behavior.
    NotImplemented is a dispatch signal, not an exception.

    Returning a negative length

    COMMON MISTAKE
    def __len__(self): return -1
    FIX
    def __len__(self): return self.pages

    What happens: len(book) raises ValueError rather than yielding a useful length.
    Validate pages at construction so the method can return a nonnegative integer.

    Using a human label as value equality

    COMMON MISTAKE
    return repr(self) == repr(other)
    FIX
    return (self.title, self.pages) == (other.title, other.pages)

    What happens: A formatting change can silently change equality semantics.
    Compare the fields that define the value, not its display text.

    Browse this lesson’s problem fixes

    Sources and further reading