pyguides

__getitem__

Python calls __getitem__ whenever you write obj[key]. The subscript can be an integer, a slice, a string, or any other key type — Python defers entirely to your implementation.

Signature

def __getitem__(self, subscript):
    ...

Python translates bracket notation into a call to this method before it reaches your code. When you write container[0], Python calls container.__getitem__(0). When you write container["name"], it calls container.__getitem__("name").

Basic Subscription

A minimal __getitem__ implementation wraps an internal list or dict:

class Bag:
    def __init__(self, items):
        self._items = items

    def __getitem__(self, key):
        return self._items[key]


bag = Bag(["a", "b", "c"])
print(bag[0])    # a
print(bag[-1])   # c

This gives you read-only subscription. The wrapped list handles integer index bounds automatically, raising IndexError when you go out of range.

Slicing

Slicing works automatically if your underlying container supports it:

grid = Bag([[1, 2], [3, 4], [5, 6]])
print(grid[0])   # [1, 2]
print(grid[1:])  # [[3, 4], [5, 6]]

When you write obj[1:3], Python passes a slice object instead of an integer. The slice has three read-only attributes: start, stop, and step. You can pass it directly to a list:

def __getitem__(self, key):
    if isinstance(key, slice):
        return self._items[key]
    return self._items[key]

Raising the Right Exceptions

This matters more than most developers realise. The Python data model specifies two distinct error paths:

SituationException to raise
Subscript type not supported (e.g., string key in a sequence)TypeError
Key valid type but out-of-bounds or missingLookupError subclass — IndexError for sequences, KeyError for mappings

For a mapping-like object, raise KeyError (which is a subclass of LookupError), not ValueError or a custom exception:

class Config:
    def __init__(self, data):
        self._data = data

    def __getitem__(self, key):
        if key not in self._data:
            raise KeyError(key)
        return self._data[key]

Bounds Checking

For fixed-size containers, you need to enforce bounds yourself:

class FixedList:
    def __init__(self, size):
        self._size = size

    def __getitem__(self, index):
        if index < 0:
            index = self._size + index
        if index < 0 or index >= self._size:
            raise IndexError(f"index {index} out of range for size {self._size}")
        return index * 2


fl = FixedList(5)
print(fl[0])   # 0
print(fl[4])   # 8
print(fl[5])   # IndexError

Python passes negative indices through to your __getitem__ method. If your object behaves like a sequence, convert negative indices yourself and then check bounds, as shown above.

Iteration Protocol

__getitem__ and __len__ together implement the sequence iteration protocol. for loops start at index 0 and increment until __getitem__ raises IndexError:

class Counter:
    def __init__(self, stop):
        self._stop = stop

    def __len__(self):
        return self._stop

    def __getitem__(self, index):
        if index >= self._stop:
            raise IndexError(index)
        return index


for i in Counter(3):
    print(i)
# output: 0
# output: 1
# output: 2

If you omit __len__, for loops will call __getitem__ starting at 0 and keep going forever — unless you raise IndexError at some point yourself.

__contains__ vs __getitem__

The in operator first checks __contains__ if it exists, then falls back to iteration. Don’t rely on __getitem__ returning a truthy value to make in work:

class Wrapper:
    def __init__(self, data):
        self._data = data

    def __getitem__(self, key):
        return self._data[key]

    def __contains__(self, item):
        return item in self._data


w = Wrapper([1, 2, 3])
print(2 in w)   # True
print(5 in w)   # False

Summary

AspectDetail
Called whenYou write obj[key]
Parameter subscript typeint, slice, or any hashable key depending on your object
SlicingPython passes a slice object; pass it directly to your underlying container
Wrong type exceptionTypeError
Wrong value / out-of-bounds exceptionIndexError (sequences) or KeyError (mappings)
Iteration with __len__for loop increments from 0 until IndexError

See Also