自定义类的__getitem__必须同时处理int和slice类型参数,切片时应调用key.indices(len(self._data))安全获取规范化索引范围,再构造同类型实例返回。

切片传给 __getitem__ 时不是 int,而是 slice 对象
当你对自定义类实例执行 obj[1:5] 或 obj[::2] 时,Python 不会传入整数索引,而是传入一个 slice 实例。如果 __getitem__ 只处理 int,就会在遇到切片时报 TypeError: 'slice' object is not subscriptable 或直接抛出 TypeError。
必须显式判断参数类型:
def __getitem__(self, key):
if isinstance(key, int):
# 处理单个索引
return self._data[key]
elif isinstance(key, slice):
# 处理切片:调用 slice.indices() 计算实际范围
start, stop, step = key.indices(len(self._data))
return [self._data[i] for i in range(start, stop, step)]
else:
raise TypeError(f"index must be int or slice, not {type(key).__name__}")
slice.indices() 是安全计算切片边界的唯一可靠方式
手动用 key.start、key.stop、key.step 计算是危险的——它们可能是 None,也可能超出数据长度,还可能为负数。Python 的切片语义(如 [10:100] 自动截断、[:-1] 支持负索引)全由 slice.indices(length) 封装。
-
key.indices(len(self._data))返回元组(start, stop, step),所有值都已规范化为非负整数且不越界 - 不要写
self._data[key]—— 这会触发递归调用,除非你明确想复用内置逻辑(比如包装 list) - 若返回新实例(如自定义序列),记得把切片结果也构造成同类型,而非裸 list
支持步长和负索引的关键就藏在 indices() 里
很多人以为要自己解析 key.step 并手动循环,其实只要用对 indices(),负步长(如 [::-1])、跨边界(如 [100:200])、省略端点(如 [:5])全被自动处理。
示例对比:
# 错误:没处理 step= -1,或没规范化 start/stop return self._data[key.start:key.stop:key.step] # 可能 IndexError 或逻辑错 <h1>正确:交给 indices() 统一规整</h1><p>start, stop, step = key.indices(len(self._data)) return CustomList([self._data[i] for i in range(start, stop, step)])</p>
注意:若 step ,<code>range(start, stop, step) 仍能正常工作(例如 range(4, -1, -1) → 4,3,2,1,0)。
别忘了切片返回值类型要和设计意图一致
如果类代表“只读视图”,返回 list 没问题;但如果希望切片结果仍是该类实例(支持链式切片、统一接口),就必须构造新实例。
- 返回
list简单,但丢失类型信息和方法 - 返回新实例需确保构造逻辑能接收切片后数据(如
CustomList(subset)) - 若底层是不可变结构(如 tuple),切片返回 tuple 更合理;若可变,通常返回同类型实例
- 不实现
__len__会导致len(obj[1:5])报错,这是常见疏漏
切片行为是否符合直觉,往往取决于 indices() 调用时机和返回值封装方式——这两处写错,用户一用就懵。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











