Python Container Protocols
Container protocols are sets of dunder methods that make a class work like a built-in container.
Start with __len__
len() simply calls your __len__ method. Return the count of the inner list.
class Playlist:
def __init__(self, name, songs):
self.name = name
self.songs = list(songs)
def __len__(self):
return len(self.songs)
mix = Playlist("Focus", ["Aurora", "Breeze", "Comet"])
print(len(mix))Add __getitem__ for indexing
Python passes each index to __getitem__. Add this method to the Playlist class.
def __getitem__(self, index):
return self.songs[index]Indexing and slicing now work
A slice arrives as one slice object. Passing it to the inner list returns a new list.
print(mix[0])
print(mix[-1])
print(mix[1:3])What __getitem__ gives for free
This class has no __iter__ yet. Python still loops by asking for index 0, 1, 2, and so on.
for song in mix:
print(song)Add __contains__ for in
The in operator calls __contains__ when you define it. Add this method to the class.
def __contains__(self, song):
return song in self.songsMembership checks read cleanly
Your method now answers the question directly. It no longer needs to scan by index.
print("Breeze" in mix)
print("Echo" in mix)Delegate iteration to the inner list
Delegation means handing a job to an inner object. The list already iterates well, so __iter__ returns its iterator.
def __iter__(self):
return iter(self.songs)Example
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
def __iter__(self):
return iter(self.songs)
mix = Playlist("Focus", ["Aurora", "Breeze", "Comet"])
print(len(mix))
print(mix[1])
print(mix[0:2])
print("Comet" in mix)
for song in mix:
print(song)Key points
- Sequence: an object you can count, index, and loop over in order
- Protocol: a set of special methods that unlock built-in behavior
- __getitem__: receives one index, or one slice object for a slice
- Use iter(inner_list) inside __iter__ to delegate the looping work
- Use __contains__ to answer in checks with your own logic
In this example, Playlist stores a name and a list of songs. __len__ returns the song count, so len(mix) prints 3. __getitem__ passes each index or slice to the inner list. mix[1] prints Breeze, and mix[0:2] prints a two-song list. __contains__ answers the in check, so "Comet" in mix is True. __iter__ returns the inner list iterator, so the loop prints each song.
Note
With only __getitem__, loops and in still work by counting indexes, which can be slow.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Python Playground
Exercise
A class defines __getitem__ with indexes starting at 0, but no __iter__. What happens in a for loop over it?