__getitem__需用isinstance(key, int)和isinstance(key, slice)分支分别处理单索引与切片;切片时必须实现__len__并调用key.indices(len(self))安全计算边界,返回类型应依设计意图选择同类型实例或list。

__getitem__ 怎么同时处理 int 和 slice 类型的 key
Python 调用 __getitem__ 时,传入的 key 可能是 int(如 obj[5]),也可能是 slice(如 obj[2:8:2])。必须显式判断类型,不能假设它是整数。
常见错误是直接对 key 做下标操作,比如 self._data[key],这在传入 slice 时会触发 TypeError: list indices must be integers or slices —— 因为底层 list 确实支持 slice,但你得确保自己逻辑里不把它当 int 用。
实操建议:
- 用
isinstance(key, slice)分支处理切片,返回新实例或子序列(取决于语义) - 用
isinstance(key, int)处理单索引,注意负索引和越界检查(IndexError) - 不要忽略
key是其他类型(如str、tuple)的可能,可抛TypeError明确拒绝
切片参数(start/stop/step)怎么安全计算边界
slice 对象的 start、stop、step 属性可能是 None,直接参与运算会出错。Python 内置类型(如 list)会自动补全默认值(start=0、stop=len(self)、step=1),但自定义类得自己算。
实操建议:
- 用
key.indices(len(self))—— 这是最关键的一步!它把slice映射成三元组(start, stop, step),自动处理None、负数、越界,并适配当前容器长度 - 别手写
if key.start is None: start = 0这类逻辑,容易漏掉 step 为负时的反转行为 - 得到三元组后,用
range(*indices)或列表推导生成结果,比手动 while 循环更安全
返回切片结果该用原类型还是 list
是否保持类型一致,取决于你的类设计意图。比如 numpy.ndarray 切片返回新 ndarray,而 str 返回新 str;但很多简单封装类直接返回 list 也能用,只是丢失了行为一致性。
实操建议:
- 如果类有状态或特殊方法(如自定义
__add__、map),返回同类型实例更合理,需在__getitem__中调用self.__class__(...) - 如果只是数据容器(如
Vector封装 list),返回list更轻量,避免构造开销 - 注意:返回
list后,用户再切片就变成list.__getitem__,不再走你的逻辑 —— 这是隐性行为切换,容易被忽略
为什么 __len__ 和 __getitem__ 配合不好会报错
切片时 Python 会调用 key.indices(len(self)),所以必须实现 __len__。没实现的话,len(self) 报 TypeError,进而导致切片失败,错误信息可能是 object of type 'X' has no len() 或更隐蔽的 AttributeError: 'slice' object has no attribute 'indices'(其实是上层调用失败了)。
另一个坑是 __len__ 返回负数或非整数 —— indices() 会直接抛 ValueError。
实操建议:
- 只要支持切片,就必须有
__len__,且返回非负整数 - 如果长度动态变化(如懒加载),
__len__应反映当前有效长度,否则indices()算出的索引会越界 - 测试时务必覆盖
obj[:]、obj[::-1]、obj[100:]这些边界 case
slice.indices() 的语义细节和与 __len__ 的耦合 —— 这两点一旦疏忽,错误表现往往不直观。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











