正确实现 __getitem__ 是支持索引操作的关键,需处理整数索引、负索引、越界检查及切片逻辑,并确保方法名正确、定义在类中、签名合法;否则会报“not subscriptable”错误。

直接支持索引操作的关键,是正确实现 __getitem__ 方法,并处理好边界、类型和切片逻辑——它不是只返回 self.data[key] 就完事的。
为什么 __getitem__ 被调用但报 TypeError: 'X' object is not subscriptable?
这个错误说明 Python 根本没找到有效的 __getitem__ 实现。常见原因:
- 方法名拼错(比如写成
__get_item__或__getitem__缺少下划线) - 定义在实例上而非类上(如
obj.__getitem__ = lambda k: ...不生效) - 方法签名不合法(必须至少接收
self和key两个参数,不能加默认值或 *args)
最简可用示例:
class MyList:
def __init__(self, items):
self._items = items
<pre class="brush:php;toolbar:false;">def __getitem__(self, key):
return self._items[key] # 直接委托给底层 listobj = MyList([10, 20, 30]) print(obj[1]) # 输出 20 —— 成功
如何安全处理整数索引、负索引和越界?
__getitem__ 不会自动帮你做边界检查;抛出什么异常、何时抛,全由你控制。Python 内建容器通常抛 IndexError,建议保持一致:
- 负索引应正常支持(
obj[-1]→ 最后一个元素) - 越界时显式
raise IndexError(f"index {key} out of range"),别让底层异常裸露(比如list的IndexError提示可能含内部细节) - 如果想模拟 NumPy 风格“越界返回 None”,得自己判断并返回,但注意这违背 Python 惯例,容易引发隐晦 bug
示例(带校验):
Python 3.14.2是Python编程语言在2025年12月5日发布的稳定版本,属于3.14系列的第二个维护更新。该版本包含了18项修复,重点解决了多进程、数据类及正则表达式等模块的回归问题,并修复了CVE-2025-12084等安全漏洞。此版本标志着自由线程模式(移除GIL)正式获得官方支持,是Python发展的重要里程碑。
def __getitem__(self, key):
if not isinstance(key, int):
raise TypeError(f"index must be int, got {type(key).__name__}")
if key = len(self._items):
raise IndexError(f"index {key} is out of range for size {len(self._items)}")
return self._items[key]
怎样让 __getitem__ 同时支持切片(obj[1:3])?
Python 传给 key 的可能是 slice 对象(如 slice(1, 3, None)),不是只有整数。不处理切片,obj[1:3] 会直接报 TypeError。
- 用
isinstance(key, slice)分支判断 - 把
slice对象交给底层容器处理(如self._items[key]),它会自动计算起止和步长 - 如果底层不是 list/tuple(比如是自定义存储结构),需手动调用
key.indices(len(self))获取规范化的(start, stop, step)
推荐写法(兼容整数与切片):
def __getitem__(self, key):
if isinstance(key, slice):
return self._items[key] # 让 list 处理切片逻辑
if isinstance(key, int):
# ... 整数校验与取值逻辑(同上)
return self._items[key]
raise TypeError(f"index must be int or slice, got {type(key).__name__}")
哪些行为不会被 __getitem__ 自动触发?
很多人误以为实现 __getitem__ 就等于有了完整序列协议——其实不是:
-
len(obj)仍会报TypeError,除非你实现__len__ -
for x in obj:会尝试调用__iter__,若未定义,则回退到__getitem__+ 从 0 开始递增索引,但一旦遇到IndexError就停止;这种回退方式不支持任意起始索引或非连续迭代 -
obj[5] = x是赋值,需要__setitem__;del obj[5]需要__delitem__
真正关键的点:切片返回新对象时,它的类型是否保留?如果 MyList[1:3] 返回的是 list 而非 MyList,就丢失了自定义行为——这时得在 __getitem__ 里对 slice 分支手动构造新实例。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










