__getitem__ 接收 int 或 slice:单索引传 int,切片传 slice 对象;需用 isinstance(key, slice) 分支处理,slice 用 key.indices(len(self._data)) 安全转 range,int 索引应主动检查越界并抛 indexerror。

__getitem__ 接收的是 int 还是 slice?
Python 在调用 __getitem__ 时,传入的参数类型取决于你用的是索引还是切片:单个值(如 obj[5])传 int,切片(如 obj[2:8] 或 obj[::2])传 slice 对象。不区分这两者,直接对参数做 index() 或 len() 就会报 TypeError: slice indices must be integers or None。
实操建议:
- 在
__getitem__开头先用isinstance(key, slice)分支判断 - 别假设
key一定可下标——slice对象没有__index__,但有start、stop、step三个属性(可能为None) - 手动处理
None:比如start or 0是错的,因为0是合法索引,应改用start if start is not None else 0
如何安全地把 slice 转成 range 并取数据?
不能直接用 range(key) —— range() 不接受 slice。得用 key.indices(len(self._data)),它返回一个三元组 (start, stop, step),且已自动处理负索引和越界(比如 obj[-3:] 或 obj[100:])。
实操建议:
- 先确保你内部有长度可查(比如有个
_data列表),否则indices()没法归一化 - 调用
key.indices(n)后,用range(*key.indices(n))得到实际要取的索引序列 - 再用列表推导或
map取出对应元素,返回新列表(不是生成器,否则切片结果无法多次遍历)
示例片段:
def __getitem__(self, key):
if isinstance(key, slice):
n = len(self._data)
start, stop, step = key.indices(n)
return [self._data[i] for i in range(start, stop, step)]
else:
return self._data[key] # int 索引走这里
__getitem__ 支持切片后,in / len / for 会自动生效吗?
不会。in 查找会调用 __contains__(默认回退到逐个调用 __getitem__,但效率低);len() 调用 __len__;for 遍历优先用 __iter__,没定义才 fallback 到 __getitem__ 从 0 开始递增索引。只写 __getitem__,不代表类“变成序列”。
实操建议:
- 显式实现
__len__(self),返回len(self._data) - 若需高效
in,重写__contains__(self, item),比如委托给self._data.__contains__(item) - 想让
for x in obj行为清晰,最好也提供__iter__(self),比如return iter(self._data)
为什么 my_list[1:100] 不报错,但 my_obj[1:100] 报 IndexError?
因为内置列表的切片是“安全截断”的,而你的 __getitem__ 如果直接用 self._data[key.start:key.stop:key.step],那没问题;但若手动算 range 后没检查索引是否越界(比如 self._data[i] 中 i 超出 len(self._data)-1),就会崩。
关键点在于:key.indices(n) 已帮你把 start/stop 归一化到 [0, n] 范围内,所以后续用 range(...) 遍历时,每个 i 都天然合法。只要用了 indices(),就不需要额外 try-except。
容易被忽略的地方:有人图省事,在 int 分支里直接 return self._data[key],却忘了 key 可能是负数或越界——这时候 __getitem__ 应该抛 IndexError,而不是靠底层列表抛,否则错误位置不清晰。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











