Speaking Python: special methods
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 write | Python calls |
|---|---|
len(x) | x.__len__() |
x[i] | x.__getitem__(i) |
repr(x), or showing x in a list | x.__repr__() |
print(x), str(x) | x.__str__(), falling back to __repr__ |
a == b | a.__eq__(b) |
v in x | x.__contains__(v) |
for v in x | x.__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) # TrueThere'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__.
Your task
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__.
- It shows as Shelf(3 jars) (not done yet)
- len() gives the number of jars (not done yet)
- Indexing works, from either end (not done yet)
- for and in work, with no __iter__ (not done yet)
The checklist ticks itself off as you work in the editor: any way that gets the result counts.
Hints come one at a time, then one way to do it. Try each before the next.
What this lesson uses, in one place.
__init__- Runs when an object is made, to set it up; self is the new object Python docs →
__repr__- The object as text for programmers: used by repr(), in lists and while debugging Python docs →
__len__- What len(x) returns; must be an int, 0 or more Python docs →
__getitem__- What x[i] (and x[a:b]) returns. Also lets for and in work, if there is no __iter__ Python docs →
__contains__- What v in x asks; without it, Python goes through the items one by one Python docs →