Skip to main content

Special methods: objects that act like built-ins

print, ==, len and + call methods with fixed names on your objects. Give a two-number Vector __repr__, __eq__ and __add__ so that Vector(1, 2) + Vector(3, 4) == Vector(4, 6) is True, then let dataclass write such methods for a Student.
P01.9.3L0 Foundation25 minIn review

Before this lesson

P01.9.2 Methods and state

After this lesson you can

  • Name the special method that print, ==, len and + call, and rewrite each as a method call.
  • Write __repr__, __eq__, __add__ and __len__ for a class of your own.
  • Explain why == compares identity when a class has no __eq__.
  • Use dataclasses.dataclass to write __init__, __repr__ and __eq__ for a record.

In review. The maths and code on this page are checked by automated tests on every change, but an independent reader has not signed it off yet. Spot a mistake or a confusing step? Tell us.

The question

2 + 3 adds, 'Ama' + 'Ben' joins text, and [55] + [65] joins lists: one sign, three meanings, picked by the type of the values. But for an object of your own, print shows a line such as <__main__.Vector object at 0x10f3a2b90>, two objects with the same numbers are not ==, and + stops with TypeError. How does + know what to do with each type, and how can your class take part?

From the last lesson

1. Rewrite b.add(80) without the short form.

Show answer

RunningMean.add(b, 80).

2. Why do all instances share a list written in the class body?

Show answer

The class body runs once, so it makes one list, stored in the class. Instances have no values of their own, so self.values finds the class’s list.

Intuition

Python turns each of these operations into a call of a method with a fixed name, written with two underscores on each side. v + w becomes v.__add__(w), v == w becomes v.__eq__(w), and so on. These are special methods; people often call them “dunder” methods, for “double underscore”.

A table for v = Vector(1, 2) and w = Vector(3, 4) with three columns: you write, Python calls, result. print(v) calls v.__repr__() (through str) and shows Vector(1, 2). v == w calls v.__eq__(w) and gives False. len(v) calls v.__len__() and gives 2. v + w calls v.__add__(w) and gives Vector(4, 6). Green arrows lead from each written form to the method it calls.
P01.9.3-F1

Each operation is a method call in disguise. A class that defines the method can use the operation.

So the meaning of + is asked first of the type of the value on its left: int has an __add__ that adds, str and list have ones that join. Your class gets an + by defining __add__.

Build it up

Step 1. The built-in types already work this way. Call the methods by name and you get what the operations give.

print((2).__add__(3), 'ab'.__add__('cd'), [1, 2].__add__([3]))
print(len([55, 65]), [55, 65].__len__())
5 abcd [1, 2, 3]
2 2

(The brackets around 2 keep Python from reading 2. as the start of a float.)

Step 2. A class without special methods. A vector of two numbers, an arrow in the plane, with only __init__:

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


v = Vector(1, 2)
print(v)                       # <__main__.Vector object at 0x10f3a2b90>
print(v == Vector(1, 2))       # False
v + Vector(3, 4)               # TypeError: unsupported operand type(s) for +: 'Vector' and 'Vector'

The number after at is where the object sits in memory, and it changes from run to run. The error message was copied from Python 3.15; the wording varies a little between versions.

Step 3. __repr__: how the object is written. It must return a string. The convention is to write the object the way you would type it.

Step 4. __eq__: when two objects are equal. Two vectors are equal when their x parts are equal and their y parts are equal.

Step 5. __add__ and __len__. __add__ builds and returns a new vector from the two it is given, without changing either; __len__ says how many numbers a vector holds.

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

    def __repr__(self):
        return f'Vector({self.x}, {self.y})'

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __len__(self):
        return 2


v = Vector(1, 2)
w = Vector(3, 4)
print(v, w)
print(v == w, v == Vector(1, 2))
print(len(v), v + w)
Vector(1, 2) Vector(3, 4)
False True
2 Vector(4, 6)
The memory diagram during v + w. The global frame holds v and w, pointing to two Vector objects, x 1 and y 2, and x 3 and y 4. Below, the frame of __add__ holds self, pointing to the same object as v, and other, pointing to the same object as w. A thick vermillion arrow labelled returns leads to a new Vector object, drawn in the result colour, with x 4 and y 6, computed as 1 + 3 and 2 + 4.
P01.9.3-F2

During v + w, self is v and other is w, as in any method call. __add__ makes a third object and returns it; v and w are not changed.

The formal statement

NoteDefinition: special method

A special method is a method whose name starts and ends with two underscores. Python calls it to carry out an operation on an object of the class: an operator, a built-in function such as len, or a statement such as print.

The operations of this lesson, for objects v and w, with what happens when the class does not define the method:

  • print(v) calls v.__str__(); without it, __repr__ is used.
  • repr(v) calls v.__repr__(); without it, you get <... at 0x...>.
  • v == w calls v.__eq__(w); without it, == is the same as is.
  • len(v) calls v.__len__(); without it, TypeError.
  • v + w calls v.__add__(w); without it, TypeError.

Some fine print, all from the Python Language Reference:

  • Printing. print turns its value into text with str, which calls __str__. The default __str__ “calls object.__repr__()” (3.3.1), so defining __repr__ alone serves both.
  • Equality. Without an __eq__, “the default behavior for equality comparison (== and !=) is based on the identity of the objects” (6.10.1): v == w is True only when v is w. With an __eq__, v != w is its opposite unless you define __ne__ too.
  • Exactly which method. For x + y, “type(x).__add__(x, y) is called” (3.3.8). That is the long form of the last lesson, RunningMean.add(a, 55), with __add__ in place of add. The lookup starts in the class, not in the instance.
  • Rules of the game. __repr__ must return a string, and __len__ a whole number that is not negative. __add__ should return a new object, as 2 + 3 does not change 2.

Worked example

Evaluate Vector(1, 2) + Vector(3, 4) == Vector(4, 6) by hand, as an expression tree (P01.2.3). + binds more tightly than ==, so the sum is worked out first.

An expression tree. The root is ==, which shows True. Its left child is +, which shows Vector(4, 6); its right child is the leaf Vector(4, 6). The children of + are the leaves Vector(1, 2) and Vector(3, 4). Numbered yellow badges give the order: 1, make Vector(1, 2); 2, make Vector(3, 4); 3, + calls __add__, which gives Vector(1 + 3, 2 + 4), that is Vector(4, 6); 4, make the right-hand Vector(4, 6); 5, == calls __eq__, 4 == 4 and 6 == 6, so True.
P01.9.3-F3

The tree, evaluated from the leaves. Each operator node is a method call on the value of its left child.

  1. Vector(1, 2) makes the first vector.
  2. Vector(3, 4) makes the second.
  3. + calls Vector(1, 2).__add__(Vector(3, 4)), which returns Vector(1 + 3, 2 + 4), a new vector with x 4 and y 6.
  4. Vector(4, 6) on the right makes a fourth object.
  5. == calls __eq__ on the sum with the fourth object as other: \({4 = 4}\) and \({6 = 6}\), so the answer is True.

The sum and the right-hand vector are two separate objects that are equal, so is would say False while == says True, just as for two equal lists in P01.6.4.

total = Vector(1, 2) + Vector(3, 4)
right = Vector(4, 6)
print(total, total == right, total is right)
print(Vector(1, 2) + Vector(3, 4) == Vector(4, 6))
Vector(4, 6) True False
True

Code

A record class such as Student of P01.9.1 needs the same three methods every time: an __init__ that stores each field, a __repr__ that shows them, and an __eq__ that compares them. The standard library writes them for you. @dataclass above a class is a decorator: a function that takes the class just made and gives it back with the methods added. The fields are listed with type hints, the notes of P01.4.2 that Python does not enforce.

from dataclasses import dataclass


@dataclass
class Student:
    name: str
    hours: int
    score: int


ama = Student('Ama', 2, 55)
print(ama)
print(ama == Student('Ama', 2, 55), ama == Student('Ama', 2, 60))
print(ama.score, vars(ama))
Student(name='Ama', hours=2, score=55)
True False
55 {'name': 'Ama', 'hours': 2, 'score': 55}
On the left, the four-line source: @dataclass, class Student:, name: str, hours: int, score: int. A green arrow labelled writes for you leads to the right, which shows in outline the three methods made: __init__(self, name, hours, score) with self.name = name and the other fields; __repr__(self), which returns Student(name='Ama', hours=2, score=55); and __eq__(self, other), which compares name, hours and score in turn. A note says no + and no len: write those yourself.
P01.9.3-F5

What @dataclass writes for a record, in outline. The fields, in order, decide the arguments of __init__, the text of __repr__ and what __eq__ compares.

The dataclasses documentation lists init, repr and eq among the methods made by default. It does not make __add__ or __len__, because it cannot guess what adding two students would mean. That is the general rule: a type decides what its operations mean. A NumPy array (P02.2) has an __add__ that adds two arrays slot by slot, which is why a + b on arrays adds, while on lists it joins.

Explore

This module’s interactive, the instance workbench, lives in P01.9.2. Each call it makes, such as a.add(55), is one lookup in the class followed by one function call; here every + and == you write is the same thing in disguise.

Pitfalls

Two panels for p = Vector(4, 6) and q = Vector(4, 6), each a memory diagram with p and q pointing to two separate Vector objects that both hold x 4 and y 6. Left, wrong, with a cross: a class without __eq__, so p == q compares the arrows, which point to different objects, and gives False. Right, with a tick: a class with __eq__, so p == q compares x with x and y with y and gives True.
P01.9.3-F4

Wrong beside right. Without __eq__, == asks whether the two arrows point to one object. With __eq__, it compares what the objects hold.

“Two objects with the same attributes are ==.”

Only if the class says so. Without __eq__, == falls back to identity, and two separate objects are never equal. This trips up tests (P01.8.4): assert result == Vector(4, 6) fails on a class without __eq__ even when the numbers are right.

“__repr__ should print the object.”

It must return a string; Python does the printing. A __repr__ that calls print returns None, and print(v) then stops with a TypeError saying that a string was expected.

“__add__ can add the other vector into self and return it.”

Then v + w changes v, and a sum written to look harmless quietly changes a value you meant to keep. 2 + 3 does not change 2; build and return a new object instead.

“v == (1, 2) will just be False.”

Our __eq__ reads other.x, and a tuple has no x, so it stops with AttributeError. Exercise 3 shows the fix: check the type of other first.

Vector

Check yourself

Recall

1. Which method does v + w call, and on which object?

Show answer

__add__, on v, the value on the left, with w as the argument: v.__add__(w).

2. What does print(v) show for a class with __repr__ but no __str__?

Show answer

The string that __repr__ returns, because the default __str__ calls __repr__.

3. Without __eq__, when is v == w True?

Show answer

Only when v and w are the same object, that is when v is w.

4. Why should __add__ return a new object?

Show answer

So that v + w leaves v and w unchanged, as 2 + 3 leaves 2 unchanged.

5. Which three methods does @dataclass write by default?

Show answer

__init__, __repr__ and __eq__.

Exercises

1. ★ Give Vector a method __sub__, so that Vector(3, 4) - Vector(1, 2) is Vector(2, 2).

Show solution
    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)

Inside the class. - calls __sub__ in the same way that + calls __add__, and \({3 - 1} = 2\), \({4 - 2} = 2\).

2. ★★ Write __mul__(self, k) so that Vector(1, 2) * 3 is Vector(3, 6). Then predict what 3 * Vector(1, 2) does, and explain why.

Show solution
    def __mul__(self, k):
        return Vector(self.x * k, self.y * k)

3 * Vector(1, 2) puts the int on the left, so Python first asks int.__mul__, which does not know vectors and answers NotImplemented. Python then tries the reflected method __rmul__ on the right operand; Vector has none, so it stops with TypeError. Adding __rmul__ = __mul__ in the class body makes both orders work.

3. ★★★ Vector(1, 2) == (1, 2) stops with AttributeError. Change __eq__ so that it gives False for anything that is not a Vector, and say what Python does with the value your method returns.

Show solution
    def __eq__(self, other):
        if not isinstance(other, Vector):
            return NotImplemented
        return self.x == other.x and self.y == other.y

NotImplemented is a special value that means “I do not know how to compare with this”. Python then tries the other side, tuple.__eq__, which also returns NotImplemented, and falls back to identity. The two are different objects, so the answer is False. Returning False directly would also work here, but NotImplemented lets the other type have its say, which is what the built-in types do.

Summary card

Operators and built-ins call special methods on your objects.

The anchor figure again: print(v) calls __repr__, v == w calls __eq__, len(v) calls __len__ and v + w calls __add__, with the results for Vector(1, 2) and Vector(3, 4).
P01.9.3-F1

Each operation is a method call.

  • Write: print(v) → __repr__, v == w → __eq__, len(v) → __len__, v + w → __add__.
  • Exact rule: x + y calls type(x).__add__(x, y).
  • Default ==: identity, the same as is.
  • Records: @dataclass writes __init__, __repr__ and __eq__ from the fields.

Sources and next steps

Sources. Each was opened and checked against this lesson on 12 November 2026.

  • Python Software Foundation, The Python Language Reference, 3.3 Special method names: 3.3.1, that __repr__ “should look like a valid Python expression”, that the default __str__ “calls object.__repr__()”, and that “x==y calls x.__eq__(y)”; 3.3.7, that __len__ is “called to implement the built-in function len()”; 3.3.8, that to evaluate x + y, “type(x).__add__(x, y) is called”. And 6.10.1 Value comparisons: “The default behavior for equality comparison (== and !=) is based on the identity of the objects.”
  • Python Software Foundation, dataclasses: Data Classes: a decorator that adds generated special methods such as __init__() and __repr__(); init, repr and eq are true by default; the generated repr has “the class name and the name and repr of each field”.
  • Ana Bell, MIT OpenCourseWare 6.100L, Fall 2022, lecture notes 18: a table of special operators, among them __add__ for self + other, __eq__ for self == other and __len__ for len(self), and the uninformative default print of an object.

Next. P01.9.4 lets one class build on another, and shows why scikit-learn’s models can all be used in the same way.

This lesson unlocks

P01.9.4 Inheritance and shared interfaces

Your progress is saved in this browser only. No account, no tracking.
Back to top