Learn Python / Special Methods

Special Methods

Why does print([1, 2]) show the list nicely, len("abc") give 3, and + join two strings? Because those types define special methods, also called dunder methods since their names start and end with double underscores, like __len__. When you define them in your own classes, your objects work with Python's built-in functions and operators too.

You never call these methods yourself. You write the normal code, and Python calls the special method behind the scenes: len(mix) runs mix.__len__(), and a + b runs a.__add__(b). That's why the names are so unusual: they mark methods that Python itself looks for.

The problem

By default, printing an object isn't very helpful:

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

book = Book("Dune", "Frank Herbert")
print(book)
Output

You get something like <__main__.Book object at 0x7f...>, which is Python's default from the object class: the class name and a memory address.

__str__: a readable description

__str__ returns the text that print() and str() show. Write it for the people using your program:

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

    def __str__(self):
        return f"{self.title} by {self.author}"

book = Book("Dune", "Frank Herbert")
print(book)
print(f"I'm reading {book}")
Output

__str__ takes only self, and it must return a string; don't print inside it. Returning anything else, like a number, is a TypeError. f-strings use __str__ too, which is why {book} shows Dune by Frank Herbert.

__repr__: a description for developers

__repr__ returns an unambiguous description, ideally one that looks like the code to recreate the object. It's what you see when an object appears inside a list, or with repr():

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

    def __str__(self):
        return f"{self.title} by {self.author}"

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

books = [Book("Dune", "Frank Herbert"), Book("Emma", "Jane Austen")]

# lists show their items with __repr__
print(books)

# print uses __str__
print(books[0])
Output

!r inside the f-string uses the repr() of each value, so the strings keep their quotes: Book('Dune', 'Frank Herbert'), which is exactly the code you'd type to make that book.

__str__ __repr__
For people using the program programmers debugging it
Used by print(), str(), f-strings repr(), items inside lists and dictionaries
Example Dune by Frank Herbert Book('Dune', 'Frank Herbert')

If you write only one of the two, write __repr__: Python falls back to it when there's no __str__.

__eq__: comparing with ==

By default, == checks whether two variables are the same object, not whether they hold the same values:

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

print(Point(1, 2) == Point(1, 2))
Output

It prints False: the two points hold the same numbers, but they're two separate objects. Define __eq__ to decide what "equal" means for your class:

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

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

print(Point(1, 2) == Point(1, 2))
print(Point(1, 2) == Point(3, 4))

# != works automatically
print(Point(1, 2) != Point(3, 4))
Output

For a == b, Python calls a.__eq__(b): self is the object on the left and other the one on the right. The method returns True or False. Once __eq__ exists, Python works out != as its opposite.

__lt__: ordering and sorting

__lt__ defines < (less than). With it, sorted(), min() and max() work on your objects without a key:

class Player:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def __lt__(self, other):
        return self.score < other.score

    def __repr__(self):
        return f"{self.name}({self.score})"

players = [Player("Mia", 1200), Player("Leo", 1850), Player("Ava", 990)]

print(sorted(players))
print(max(players))
print(players[0] < players[1])
Output

sorted() and max() only ever ask "is this one less than that one?", so __lt__ alone is enough for them. Here a player is "less than" another when their score is lower, so sorted() puts Ava (990) first and max() gives Leo (1850). __repr__ is there so the results print readably.

The other comparisons have special methods too: __le__ (<=), __gt__ (>) and __ge__ (>=).

__len__, __getitem__ and __contains__

These three let your object act like a collection:

  • __len__ makes len(obj) work.
  • __getitem__ makes obj[index] work, and that's enough to loop over it too.
  • __contains__ makes item in obj work.
class Playlist:
    def __init__(self, name, songs):
        self.name = name
        self.songs = list(songs)

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

    def __getitem__(self, index):
        return self.songs[index]

    def __contains__(self, song):
        return song in self.songs

mix = Playlist("Road trip", ["Africa", "Wonderwall", "Hey Jude"])

print(len(mix))
print(mix[0])
print(mix[-1])
print("Hey Jude" in mix)

for song in mix:
    print("-", song)
Output

Each method passes the work on to the list inside, self.songs. Because __getitem__ hands the index straight to the list, negative indexes like mix[-1] and even slices like mix[0:2] work too. For the for loop, Python calls __getitem__ with 0, 1, 2, โ€ฆ until the list raises an IndexError, which tells it to stop.

Arithmetic operators

Define __add__, __sub__, __mul__ and friends to make +, - and * work. This is called operator overloading:

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

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

    def __mul__(self, factor):
        return Vector(self.x * factor, self.y * factor)

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

a = Vector(1, 2)
b = Vector(3, 4)

print(a + b)
print(a * 3)
Output

a + b runs a.__add__(b), which builds and returns a new Vector(4, 6); a and b don't change, just as 2 + 3 doesn't change the 2. a * 3 runs a.__mul__(3), so factor is 3. The order matters: 3 * a would ask the number 3 how to multiply by a Vector, and it doesn't know, so that's a TypeError (a separate method, __rmul__, handles that case).

Operator Special method
a + b __add__
a - b __sub__
a * b __mul__
a / b __truediv__
a == b __eq__
a < b __lt__
len(a) __len__
a[i] __getitem__
x in a __contains__
str(a) / print(a) __str__
repr(a) __repr__

A shortcut: dataclasses

Writing __init__, __repr__ and __eq__ by hand gets repetitive. The dataclasses module writes them for you:

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

p = Point(3, 4)
print(p)
print(p == Point(3, 4))
print(p.x + p.y)
Output

The x: int lines are type hints, covered in the Type Hints lesson. For simple classes that mainly hold data, dataclasses save a lot of typing.

Exercises

Exercise 1: Printable money

Add __str__ to the Money class so that print() shows 12.50 EUR.

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


print(Money(12.5, "EUR"))
Output

Exercise 2: Adding money

Now add __add__ so two Money objects with the same currency can be added. If the currencies differ, raise a ValueError.

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

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


print(Money(12.5, "EUR") + Money(7.25, "EUR"))   # 19.75 EUR
Output

Exercise 3: Equal cards

Add __eq__ so two cards are equal when both rank and suit match, and __repr__ so a card shows as Card('A', 'spades').

class Card:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit


print(Card("A", "spades") == Card("A", "spades"))   # True
print(Card("A", "spades") == Card("K", "spades"))   # False
print([Card("Q", "hearts")])                        # [Card('Q', 'hearts')]
Output

Exercise 4: A word bag

Give WordBag a __len__ that returns the number of words and a __contains__ that checks for a word, ignoring case.

class WordBag:
    def __init__(self, text):
        self.words = text.lower().split()


bag = WordBag("The quick brown fox")
print(len(bag))          # 4
print("QUICK" in bag)    # True
print("dog" in bag)      # False
Output

Special methods at a glance

A quick reference for later. You never call these methods yourself; Python calls them when you use the matching syntax.

Method Python calls it for Typical use
__init__(self, ...) Point(1, 2) Setting up a new object
__str__(self) print(p), str(p), f"{p}" A readable description for users
__repr__(self) repr(p), and when the object is shown inside a list An exact description for developers
__eq__(self, other) p == q (and !=) Comparing two objects by value
__lt__(self, other) p < q, and sorted(), min(), max() Ordering objects
__le__, __gt__, __ge__ <=, >, >= The other comparisons
__len__(self) len(p) The object's size
__getitem__(self, key) p[key], and for loops if there's no __iter__ Indexing like a list or dictionary
__contains__(self, item) item in p Membership tests
__iter__(self) for x in p Looping (see Iterators and Generators)
__add__(self, other) p + q Adding objects
__sub__, __mul__, __truediv__ -, *, / The other arithmetic operators
__bool__(self) if p:, bool(p) Deciding whether the object counts as true
__call__(self, ...) p(...) Letting an object be called like a function
__hash__(self) Putting the object in a set or using it as a dictionary key Usually written alongside __eq__

@dataclass writes __init__, __repr__ and __eq__ for you, as you saw above.

Summary

  • Special (dunder) methods let your objects work with built-in functions and operators.
  • __str__ is for readable output, and __repr__ is the developer view, used inside lists.
  • __eq__ defines ==, and __lt__ defines <, which makes sorting work.
  • __len__, __getitem__ and __contains__ make an object behave like a collection.
  • __add__, __mul__ and friends overload arithmetic operators.
  • @dataclass generates __init__, __repr__ and __eq__ for simple data classes.

Share this tutorial

Visualize

OUTPUT
No visualization yet
Press Play, or the Visualize button, to record your program and step through it.

Program input

Your code calls input(). Visualize records the whole run in one pass, so enter the values now, one per line, in the order the program asks for them.

If the program asks for more values than you give, it stops with an EOFError.

Send feedback