__getitem__ 必须显式处理 slice 对象,通过 isinstance(key, slice) 分支调用 key.indices(len(self._data)) 获取标准化索引范围,并返回当前类新实例而非 list,整数索引则直接返回单个元素。

__getitem__ 接收的是 slice 对象,不是元组或索引列表
当你对自定义类实例执行 obj[1:5] 或 obj[::2] 时,Python 不会把切片拆成多个整数调用,而是直接传入一个 slice 实例。如果 __getitem__ 只处理 int 类型,遇到切片就会抛 TypeError: 'slice' object is not subscriptable。
实操建议:
- 必须在
__getitem__中显式判断参数类型:isinstance(key, slice) -
slice对象有三个属性:.start、.stop、.step,可能为None(比如[::2]中的start和stop) - 不要手动计算索引范围——用
range(*key.indices(len(self._data)))最安全,它自动处理负数、越界和None
切片返回值该用什么类型?别直接返回 list
原生容器如 list、str 的切片返回同类型新实例,这是协议一致性要求。如果你的类叫 IntVector,obj[2:4] 返回 list 会破坏接口预期,调用方无法继续链式切片或调用专属方法。
实操建议:
- 切片结果应构造并返回 **当前类的新实例**,而非内置类型
- 确保构造逻辑能接收索引序列(例如传入
self._data[i]的子集) - 如果内部数据是不可变结构(如
tuple),切片后仍可用相同类型封装,避免意外可变性
负索引和省略边界在 slice.indices() 里统一处理
手写逻辑解析 start/stop/step 容易出错:比如 obj[-3:-1] 在长度为 5 的序列中实际对应索引 2 和 4,而 obj[:] 的 start=None、stop=None 需映射为 0 和 len(self)。自己算容易漏边界或符号反转。
实操建议:
- 始终用
key.indices(n)获取标准化三元组:(start, stop, step) -
n是你的数据长度(不是硬编码的数字),否则越界行为不一致 -
indices()返回的stop是**上界**(类似range),直接用于range(start, stop, step)即可遍历有效下标
__getitem__ 里混用 int 和 slice 逻辑要隔离
常见错误是把整数索引和切片逻辑写在一起,用一堆 if/elif 判断,导致分支膨胀、step != 1 处理遗漏、或忘记返回单个元素(而非长度为 1 的切片结果)。
实操建议:
- 先分两大分支:
if isinstance(key, slice): ... else: ... - 整数分支直接返回单个元素(类型保持一致,比如
self._data[key]) - 切片分支统一走
range(*key.indices(...))构造索引列表,再按需取值、封装新实例 - 不要在切片分支里调用
self[i]循环取值——这会再次触发__getitem__,可能引发递归或性能问题
真正麻烦的不是写切片支持,而是让 slice.indices() 和你内部存储的索引语义完全对齐;比如你的数据实际是稀疏数组或懒加载序列,这时 indices() 给的范围只是“逻辑索引”,还需映射到物理存储位置。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











