__len__, __getitem__, __setitem__
Overview
In Python, using square brackets like `obj[key]` and calling `len(obj)` do not have special compiler-level behavior for custom classes. Instead, they are routed directly to specific 'dunder' (double underscore) methods on the object: `__getitem__(self, key)`, `__setitem__(self, key, value)`, and `__len__(self)`. class RowSet: def __init__(self, rows): self.rows = rows def __getitem__(self, index): return self.rows[index] def __len__(self): return len(self.rows) rs = RowSet(['a', 'b', 'c']) print(rs[0]) # 'a' -- calls rs.__getitem__(0) print(len(rs)) # 3 -- calls rs.__len__() Any custom class that implements these methods will seamlessly behave like a built-in list or dictionary — no inheritance required. Analogy: Dunder methods are like standardized electrical outlets in a house. You don't need to know how the electricity is generated inside the appliance; as long as the appliance has the right plug (implements the right dunder method), you can plug it into the wall (use standard Python syntax like `len()` or `[]`).
Implementing these methods allows custom objects to integrate natively with Python's built-in syntax. This makes your code more intuitive and 'Pythonic' by relying on standard operators instead of custom methods like `obj.get_value(key)`.
Where used: Custom configuration managers, Data processing pipelines, Wrappers around external APIs
Why learn this
- Building custom container classes that behave exactly like built-in lists or dictionaries.
- Understanding how data science libraries like Pandas or PyTorch allow row/column access via square brackets.
- Writing intuitive, standard APIs that other Python developers already know how to use.
Code walkthrough
class TrainCar:
def __init__(self, passengers):
self.passengers = passengers
def __getitem__(self, index):
return self.passengers[index]
car = TrainCar(['Alice', 'Bob'])
print(car[0])
Focus: print(car[0])
Aha moment
class MagicBox:
def __getitem__(self, key):
return key * 2
box = MagicBox()
print(box[10])
print(box['hello '])
Prediction: What happens when we access `box[10]` and `box['hello ']` even though `MagicBox` has no internal data?
Common guess: It raises an IndexError or KeyError because the box is empty and has no underlying list or dictionary.
`__getitem__` is simply a method call. It doesn't actually need to look up data in a dictionary or list; it receives the key inside the brackets as an argument and can return any dynamically computed value.
Common mistakes
- Forgetting to raise KeyError or IndexError: If `__getitem__` is asked for an invalid key or index, you must explicitly raise `KeyError` or `IndexError` instead of returning `None` or raising a generic `Exception`. Python relies on these specific errors.
- Returning the wrong type from __len__: `__len__` must strictly return a non-negative integer. Returning a float, a string, or a negative number will cause Python to raise a `TypeError` or `ValueError`.
Glossary
- dunder
- A special built-in method in Python that has double underscores before and after its name, e.g. `__len__` or `__getitem__`.
- Pythonic
- Writing code in a way that is natural, concise, and follows the best practices of the Python programming language, e.g. using `len(obj)` instead of `obj.get_length()`.
Recall questions
- What method is called under the hood when you use square bracket syntax to read a value, like `my_obj['key']`?
- What method must a class implement for the built-in `len()` function to work on its instances?
- Why is it generally better to implement `__getitem__` instead of creating a custom method like `get_value()`?
Understanding checks
What will the following code output?
40
The square bracket syntax `box[1]` internally calls the `__getitem__` method with `1` as the `index` argument. The method accesses `self.contents[1]` which is `20`, multiplies it by `2`, and returns `40`.
The following class is intended to allow getting its length using `len(bag)`. However, it raises a TypeError. What is wrong?
`__len__` must return an integer, but it is returning a string.
Python strictly enforces that the `__len__` dunder method returns a non-negative integer. Returning a string causes a TypeError when the built-in `len()` function is called on the object.
Practice tasks
Implement a simple Dictionary Wrapper
The `Config` class uses custom methods to read, set, and get the length of the data. Modify the class to use standard python syntax by replacing these methods with the appropriate `__getitem__`, `__setitem__`, and `__len__` dunder methods.
Challenge
A Repeating Sequence
Create a class `Repeater` initialized with a string. Its `__len__` should return the length of the string. When accessed with `__getitem__`, it should return the character at the given index, wrapping around indefinitely if the index is larger than the string length. (Assume positive indices). For example, `Repeater('abc')[4]` should return `'b'`.
Continue learning
Previous: Everything is an object in Python
Previous: Defining functions and return values
Previous: Lists: indexing, slicing, methods
Previous: Dictionaries: operations and use cases
Previous: __init__ and __new__
Next: The Iterable Protocol