Sometimes you want to use subscript operations (bracket notation) like obj[0] or obj["key"] on your custom classes, just like with Python lists and dictionaries. To achieve this, you need to implement specific special methods within the class definition. This allows objects to behave like containers.
This article explains the four special methods required to realize subscript operations, along with implementation examples.
4 Special Methods Required for Subscript Operations
Define the following methods to manipulate objects like lists or dictionaries:
__len__(self): Defines the behavior oflen(obj). Returns the number of elements.__getitem__(self, key): Defines the behavior ofobj[key]. Responsible for retrieving values.__setitem__(self, key, value): Defines the behavior ofobj[key] = value. Responsible for updating or adding values.__delitem__(self, key): Defines the behavior ofdel obj[key]. Responsible for deleting elements.
Implementation Example: High-Functionality Team Member Management Class
As an example, let’s create a Team class that manages team member names in a list. In addition to simple list functionality, this class adds the following customizations:
- Get, update, delete members by index (number).
- Validation of member names (error if not a string).
- Function to get the member’s management number (index) by specifying a string (name).
class Team:
def __init__(self, *members):
"""Initialization: Accept members as variable-length arguments and store in a list"""
self._members = list(members)
def __len__(self):
"""Behavior of len(obj): Returns the number of members"""
return len(self._members)
def __getitem__(self, key):
"""Behavior of obj[key]: Retrieve value"""
# If index (integer)
if isinstance(key, int):
if 0 <= key < len(self._members):
return self._members[key]
else:
raise IndexError("Specified index is out of range.")
# If string (name): Returns the index of that member (Dictionary-like behavior)
elif isinstance(key, str):
if key in self._members:
return self._members.index(key)
else:
raise ValueError(f"Member '{key}' does not exist.")
else:
raise TypeError("Index must be an integer or string.")
def __setitem__(self, index, value):
"""Behavior of obj[index] = value: Update value"""
# Design to allow only index specification here
if not isinstance(index, int):
raise TypeError("Please specify index as an integer for update.")
if not isinstance(value, str):
raise TypeError("Member name must be a string.")
if 0 <= index < len(self._members):
print(f"[Update] Changing {self._members[index]} to {value}.")
self._members[index] = value
else:
raise IndexError("Specified index is out of range.")
def __delitem__(self, index):
"""Behavior of del obj[index]: Delete element"""
if 0 <= index < len(self._members):
removed = self._members.pop(index)
print(f"[Delete] Removed {removed} from the team.")
else:
raise IndexError("Specified index is out of range.")
def __repr__(self):
"""String representation of the object"""
return f"Team({self._members})"
# --- Verification ---
# Create team
my_team = Team("Tanaka", "Sato", "Suzuki", "Takahashi")
print(f"Initial State: {my_team}")
print(f"Count: {len(my_team)}")
# 1. __getitem__ (Index reference)
print(f"2nd Member: {my_team[1]}")
# 2. __getitem__ (Reference by string key - Custom function)
# Reverse lookup index from name
try:
idx = my_team["Suzuki"]
print(f"Index of 'Suzuki': {idx}")
except ValueError as e:
print(e)
# 3. __setitem__ (Update)
# Validation causes error if trying to assign non-string
try:
my_team[0] = "Yamada" # Normal update
# my_team[0] = 123 # Error (TypeError)
except TypeError as e:
print(e)
print(f"After Update: {my_team}")
# 4. __delitem__ (Delete)
del my_team[2] # Delete "Suzuki"
print(f"After Delete: {my_team}")
Output:
Initial State: Team(['Tanaka', 'Sato', 'Suzuki', 'Takahashi'])
Count: 4
2nd Member: Sato
Index of 'Suzuki': 2
[Update] Changing Tanaka to Yamada.
After Update: Team(['Yamada', 'Sato', 'Suzuki', 'Takahashi'])
[Delete] Removed Suzuki from the team.
After Delete: Team(['Yamada', 'Sato', 'Takahashi'])
Explanation: Flexible __getitem__ Implementation
A key point in the code above is the type checking (isinstance) within the __getitem__ method.
def __getitem__(self, key):
if isinstance(key, int):
# If integer, process as list index
...
elif isinstance(key, str):
# If string, process like a dictionary key (search here)
...
By implementing it this way, you can support both “list-like operations (obj[0])” and “dictionary-like operations (obj['name'])” for a single class.
Summary
To perform operations using [] in a custom class, implement the following special methods:
__getitem__: Reference__setitem__: Assignment__delitem__: Deletion
Implementing __len__ allows support for the len() function. By determining the type of the key argument within __getitem__, you can provide flexible access methods such as index numbers and string keys. Utilizing these allows you to design classes that can be handled intuitively, just like Python’s built-in types.
