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.

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

__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

__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")]
print(books)        # lists use __repr__ for their items
print(books[0])     # print uses __str__
Output

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

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))
print(Point(1, 2) != Point(3, 4))   # != works automatically
Output

__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

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

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
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.