`__setitem__` defines what happens when an object is assigned through subscription syntax: `obj[index] = value`. Python calls the method automatically. Define it when a custom object needs to accept, validate, or redirect item assignments. section 17.7.2 section 17.8
Assigning to a typed list
A `TypedList` can check an incoming element before storing it in an underlying list: `x[2] = "Hello"` calls `__setitem__`, which checks the type and then assigns to `self.elements[2]`. Reading `x[2]` is a separate job for `__getitem__`. section 17.8
class TypedList:
def __init__(self, example_element, initial_list=[]):
self.type = type(example_element)
if not isinstance(initial_list, list):
raise TypeError("Second argument of TypedList must "
"be a list.")
for element in initial_list:
self.__check(element)
self.elements = initial_list[:]
def __check(self, element):
if type(element) != self.type:
raise TypeError("Attempted to add an element of "
"incorrect type to a typed list.")
def __setitem__(self, i, element):
self.__check(element)
self.elements[i] = element
def __getitem__(self, i):
return self.elements[i]
The implementation places the type check immediately before assignment to the stored list. section 17.8
Subclassing a built-in collection avoids reimplementing its other operations. An overriding `__setitem__` can check a value, then call `super().__setitem__(index, value)`. Assignment alone is not the whole collection interface: a custom list also needs other methods, and slice assignment needs explicit handling. section 17.9.1 section 17.8
Restricting dictionary entries
A `dict` subclass can reject a key or value that is not a string, raise `TypeError`, and delegate valid assignments to the parent method. Do not rely on `__setitem__` alone to validate construction from another dictionary or key-value tuples: those paths can bypass it. Check existing entries in an overridden `__init__` as well. section 17.11 section 17.11.2
""" Create a dictionary that allows only strings for keys and values"""
class StringDict(dict):
def __setitem__(self, key, value):
if not isinstance(key, str):
raise TypeError('key is not a string') #1
if not isinstance(value, str):
raise TypeError('value is not a string')
super().__setitem__(key, value)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
key_error = any(not isinstance(_, str)
for _ in self.keys()) #2
if key_error:
raise TypeError('key is not a string')
value_error = any(not isinstance(_, str)
for _ in self.values()) #2
if value_error:
raise TypeError('value is not a string')
The complete dictionary example checks assignments in `__setitem__` and entries supplied at construction in `__init__`. section 17.11.2
