__getitem__ 能响应 [i] 和 [i:j] 是因为 python 解释器统一调用该方法并传入 int 或 slice 类型的 key;需在方法内用 isinstance 分支处理,切片时用 slice.indices(len(self)) 安全转换并返回同类实例。

为什么 __getitem__ 能响应 [i] 和 [i:j] 两种调用
Python 解释器在遇到 obj[key] 语法时,会直接尝试调用 obj.__getitem__(key),不管 key 是 int、slice 还是其他类型。这意味着你不用额外判断语法形式,只需在 __getitem__ 内区分参数类型即可。
常见错误是只处理整数索引,结果一用切片就报 TypeError: slice indices must be integers or None——其实是你没接住 slice 对象,反而把它传给了底层 list 或 array 的索引逻辑。
-
key是int:对应单个元素访问,如obj[5] -
key是slice:对应切片,如obj[2:6]、obj[::2],其属性为.start、.stop、.step -
key是其他类型(如str、tuple):只要你的逻辑支持,也可以处理,比如实现字段名访问
如何安全地支持切片并返回同类实例
多数自定义容器希望切片后仍返回自身类型(比如 MyList[1:4] 返回 MyList,而非 list),这就需要你在 __getitem__ 中识别 slice 并手动构造新实例。
注意 slice.indices(len(self)) ——它能把任意 slice(含 None 或负数)转成明确的 (start, stop, step) 三元组,避免自己手写边界校验。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
def __getitem__(self, key):
if isinstance(key, slice):
# 将 slice 映射到合法索引范围
start, stop, step = key.indices(len(self))
# 构造新实例,传入切片后的数据(假设内部用 list 存储)
return self.__class__(self._data[start:stop:step])
elif isinstance(key, int):
# 处理负索引和越界
if key <h3>
<code>__getitem__</code> 不处理赋值和删除,别和 <code>__setitem__</code>/<code>__delitem__</code> 混用</h3><p><code>__getitem__</code> 只负责读取,哪怕你返回了可变对象(比如内部列表),对返回值的修改也不会触发任何 magic method。用户写 <code>obj[0].append(x)</code> 是合法的,但这是在操作底层对象,不是你的类在“响应赋值”。</p><p>如果要支持 <code>obj[i] = x</code> 或 <code>del obj[i]</code>,必须单独实现 <code>__setitem__</code> 和 <code>__delitem__</code>,且它们的参数规则与 <code>__getitem__</code> 一致(同样要处理 <code>int</code> 和 <code>slice</code>)。</p>
- 误以为实现了
__getitem__就能用=赋值 → 实际会抛TypeError: 'MyClass' object does not support item assignment - 在
__getitem__里偷偷修改self._data→ 违反只读语义,容易引发隐蔽 bug - 对
slice参数返回原 list 切片(如self._data[key])→ 返回的是list,不是你的类实例,链式调用断裂
性能与兼容性要注意的两个细节
切片行为是否支持步长(step ≠ 1)和空切片(start ≥ stop),直接影响下游库(如 NumPy、pandas)能否将你的类当容器使用。很多工具会试探性调用 obj[0:0:2] 来检测切片能力。
- 不要在
__getitem__开头就if key == slice(None): return self.copy()——这漏掉了obj[::2]等非全量切片 - 若内部数据结构不支持高效随机切片(比如链表),应尽早抛出
NotImplementedError,而不是默默降级为遍历生成,否则性能雪崩 - Python 3.9+ 支持
typing.Self,返回类型注解可写-> Self,但运行时不生效,仅辅助静态检查
最常被忽略的一点:slice.indices() 的长度参数必须是当前对象的逻辑长度(比如你重写了 __len__,就要用那个值),而不是内部存储的长度——否则负索引计算和越界判断都会错位。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










