Skip to content

Speaking Python: special methods

AdvancedLesson 11 of 149 min

How len(), indexing, for and in work on your own classes: Python calls special methods like __len__ and __getitem__. Build a class that acts like a list.

len("hello") and len([1, 2]) both work, but len doesn't know about strings or lists. It asks the object. Behind the scenes, len(x) calls a method named __len__ on x's type:

word = "hello"
print(len(word), word.__len__())   # 5 5
print(type(word).__len__)

Methods with two underscores on each side are special methods (people say "dunder" methods, for double underscore). You don't usually call them yourself. You write them, and Python calls them when your object meets the matching syntax:

You writePython calls
len(x)x.__len__()
x[i]x.__getitem__(i)
repr(x), or showing x in a listx.__repr__()
print(x), str(x)x.__str__(), falling back to __repr__
a == ba.__eq__(b)
v in xx.__contains__(v)
for v in xx.__iter__()
Thing(…)Thing.__init__(the_new_object, …)

That's the deal Python offers: implement the methods, and your objects work with the built-in functions and syntax everyone already knows.

Tip:A quick reminder about classes: class Trail: makes a new type, and calling Trail(…) makes an object of it. Python then runs __init__ on the new object, which is passed in as self, so self.stops = … stores a value on that object. Every method takes self first.

A class that acts like a sequence

Here's a walking trail with numbered stops. With two special methods, len, indexing, slicing, for and in all work on it:

class Trail:
    def __init__(self, name, stops):
        self.name = name
        self.stops = list(stops)

    def __repr__(self):
        return f"Trail({self.name!r}, {len(self.stops)} stops)"

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

    def __getitem__(self, position):
        return self.stops[position]


ridge = Trail("Ridge loop", ["car park", "waterfall", "summit", "hut"])
print(ridge)               # Trail('Ridge loop', 4 stops)
print(len(ridge), ridge[0], ridge[-1])
print(ridge[1:3])          # the slice is passed straight through to the list
for stop in ridge:
    print("->", stop)
print("summit" in ridge)   # True

There's no __iter__ or __contains__ here, yet for and in work. When they're missing, Python falls back to calling __getitem__ with 0, 1, 2… until it raises IndexError, which the list does for us at the end. Write __iter__ and __contains__ when you want more control or speed (the next lesson covers __iter__).

Without a __repr__, print(ridge) shows something like <__main__.Trail object at 0x…>: the type and the identity, and nothing about the value. A good __repr__ is the most useful special method you can write, because it's what you see in errors, in lists and while debugging.

Try it:Delete __getitem__ from Trail and run it again. Read which line fails first, and the error's wording.

More in the Python docs: special method names, emulating containers and membership tests.

Your turn

Finish the Shelf class so it acts like a sequence of jars. Printing Shelf(["jam", "honey", "pickles"]) should show Shelf(3 jars), len() should give 3, pantry[0] should be "jam" (and pantry[-1] "pickles"), and for and in should work. Write only __repr__, __len__ and __getitem__.

Next →