Python __setitem__ Method
Last modified April 8, 2025
This comprehensive guide explores Python's __setitem__ method, the
special method that enables object subscription with square brackets. We'll cover
basic usage, sequence protocols, mapping protocols, and practical examples.
Basic Definitions
The __setitem__ method is called to implement assignment to
subscripted objects using the syntax obj[key] = value. It allows
objects to emulate container types.
Key characteristics: it must accept at least two arguments (self and key), can accept a third value argument, and is invoked for slice operations. It enables mutable sequence and mapping behavior in custom classes.
Basic __setitem__ Implementation
Here's the simplest implementation showing how __setitem__ enables
subscript assignment. This demonstrates the basic syntax and behavior.
class SimpleDict:
    def __init__(self):
        self._data = {}
    
    def __setitem__(self, key, value):
        print(f"Setting {key} to {value}")
        self._data[key] = value
    
    def __getitem__(self, key):
        return self._data[key]
d = SimpleDict()
d['name'] = 'Alice'  # Calls __setitem__
print(d['name'])     # Calls __getitem__
This example creates a simple dictionary-like class. The __setitem__
method intercepts square bracket assignments and stores them in an internal dict.
The output shows the method being called when we assign to d['name'].
This pattern is fundamental to creating custom container types in Python.
Implementing a Sequence Type
__setitem__ is essential when creating mutable sequence types that
support index assignment. This example shows a custom list implementation.
class MyList:
    def __init__(self, items=None):
        self._items = list(items) if items else []
    
    def __setitem__(self, index, value):
        if isinstance(index, slice):
            self._items[index] = value
        else:
            self._items[index] = value
    
    def __getitem__(self, index):
        return self._items[index]
    
    def __repr__(self):
        return repr(self._items)
lst = MyList([1, 2, 3])
lst[1] = 99       # Single index assignment
lst[0:2] = [7, 8] # Slice assignment
print(lst)        # [7, 8, 3]
This custom list handles both single index and slice assignments through
__setitem__. The method checks the index type to handle both cases.
The slice handling is automatic when using a list internally, but demonstrates
how Python translates slice syntax to __setitem__ calls.
Creating a Restricted Dictionary
__setitem__ can enforce constraints on what values can be assigned.
This example creates a dictionary that only accepts numeric values.
class NumericDict:
    def __init__(self):
        self._data = {}
    
    def __setitem__(self, key, value):
        if not isinstance(value, (int, float)):
            raise ValueError("Only numeric values allowed")
        self._data[key] = value
    
    def __getitem__(self, key):
        return self._data[key]
nd = NumericDict()
nd['age'] = 25    # Works
# nd['name'] = 'Bob'  # Raises ValueError
print(nd['age'])
This dictionary subclass validates values before allowing assignment. The
__setitem__ method checks the value type before storing it.
This pattern is useful for creating constrained containers where you need to enforce specific data types or validation rules on stored values.
Matrix Class with 2D Indexing
__setitem__ can handle multi-dimensional indexing by accepting
tuples as keys. This example implements a simple matrix class.
class Matrix:
    def __init__(self, rows, cols):
        self.rows = rows
        self.cols = cols
        self._data = [[0] * cols for _ in range(rows)]
    
    def __setitem__(self, key, value):
        row, col = key
        if 0 <= row < self.rows and 0 <= col < self.cols:
            self._data[row][col] = value
        else:
            raise IndexError("Matrix indices out of range")
    
    def __getitem__(self, key):
        row, col = key
        return self._data[row][col]
    
    def __repr__(self):
        return '\n'.join(' '.join(map(str, row)) for row in self._data)
m = Matrix(3, 3)
m[1, 1] = 5  # Center cell
m[0, 2] = 3  # Top-right cell
print(m)
This matrix class accepts two-dimensional indexing using tuple notation.
__setitem__ unpacks the tuple into row and column indices.
The method includes bounds checking to ensure indices are valid before assignment. This demonstrates how to implement complex container behavior.
Proxy Pattern with __setitem__
__setitem__ can be used to implement the proxy pattern, where
access to another object is controlled or modified. This example shows a logging
proxy.
class LoggingListProxy:
    def __init__(self, original):
        self._original = original
    
    def __setitem__(self, index, value):
        print(f"Setting index {index} to {value}")
        self._original[index] = value
    
    def __getitem__(self, index):
        return self._original[index]
    
    def __repr__(self):
        return repr(self._original)
original = [1, 2, 3]
proxy = LoggingListProxy(original)
proxy[1] = 99
print(original)  # [1, 99, 3]
This proxy wraps a list and logs all assignment operations. The actual storage still happens in the original list, but we intercept and log the operations.
Proxy patterns are useful for adding behavior like logging, validation, or access control without modifying the original object's class.
Best Practices
- Maintain consistency: Ensure __setitem__ works with __getitem__ and __delitem__
- Handle slices: Consider implementing slice support for sequence types
- Validate inputs: Check key and value types when appropriate
- Document behavior: Clearly document any special assignment rules
- Consider performance: __setitem__ is called frequently in loops
Source References
Author
List all Python tutorials.