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 教程。