Python __setitem__ 方法
最后修改于 2025 年 4 月 8 日
这篇全面的指南探讨了 Python 的 __setitem__ 方法,这个特殊方法使得对象可以通过方括号进行下标操作。我们将涵盖基本用法、序列协议、映射协议和实际示例。
基本定义
__setitem__ 方法被调用来实现使用语法 obj[key] = value 对下标对象的赋值。它允许对象模拟容器类型。
主要特征:它必须接受至少两个参数(self 和 key),可以接受第三个 value 参数,并且针对切片操作会被调用。它能够在自定义类中实现可变序列和映射行为。
基本的 __setitem__ 实现
这是一个最简单的实现,展示了 __setitem__ 如何实现下标赋值。这演示了基本的语法和行为。
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__
这个例子创建了一个简单的类似字典的类。__setitem__ 方法拦截方括号赋值并将它们存储在内部 dict 中。
输出显示了当我们赋值给 d['name'] 时该方法被调用。这种模式是在 Python 中创建自定义容器类型的基础。
实现一个序列类型
当创建支持索引赋值的可变序列类型时,__setitem__ 是必不可少的。这个例子展示了一个自定义列表的实现。
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]
这个自定义列表通过 __setitem__ 处理单个索引和切片赋值。该方法检查索引类型来处理这两种情况。
当在内部使用列表时,切片处理是自动的,但演示了 Python 如何将切片语法转换为 __setitem__ 调用。
创建一个受限的字典
__setitem__ 可以对可以分配的值强制执行约束。这个例子创建了一个只接受数值的字典。
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'])
这个字典子类在允许赋值之前验证值。__setitem__ 方法在存储值之前检查值类型。
这种模式对于创建受约束的容器非常有用,在这些容器中,您需要对存储的值强制执行特定的数据类型或验证规则。
具有 2D 索引的矩阵类
__setitem__ 可以通过接受元组作为键来处理多维索引。这个例子实现了一个简单的矩阵类。
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)
这个矩阵类接受使用元组符号的二维索引。 __setitem__ 将元组解包为行和列索引。
该方法包括边界检查,以确保索引在赋值之前有效。这演示了如何实现复杂的容器行为。
带有 __setitem__ 的代理模式
__setitem__ 可以用于实现代理模式,在这种模式中,对另一个对象的访问受到控制或修改。这个例子展示了一个日志代理。
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]
这个代理包装了一个列表并记录所有赋值操作。实际的存储仍然发生在原始列表中,但我们拦截并记录这些操作。
代理模式对于添加诸如日志记录、验证或访问控制之类的行为非常有用,而无需修改原始对象的类。
最佳实践
- 保持一致性: 确保 __setitem__ 与 __getitem__ 和 __delitem__ 一起工作
- 处理切片: 考虑为序列类型实现切片支持
- 验证输入: 在适当的时候检查键和值的类型
- 文档行为: 清楚地记录任何特殊的赋值规则
- 考虑性能: __setitem__ 在循环中经常被调用
资料来源
作者
列出所有 Python 教程。