切片传给__getitem__的是slice对象而非元组;应使用key.indices(len(self._data))获取归一化三元组,它自动处理None、负索引、越界及空切片,确保行为与内置类型一致。

切片传给 __getitem__ 的是 slice 对象,不是元组
很多人写错的第一步:以为切片会以 (start, stop, step) 元组形式传入。实际传入的是 slice 实例,比如 a[1:5:2] 会调用 __getitem__(slice(1, 5, 2))。直接对 slice 做索引会报 TypeError: slice indices must be integers or None or have an __index__ method。
实操建议:
- 在
__getitem__中先用isinstance(key, slice)分支判断 - 别直接用
key.start等属性参与计算——要先处理None(Python 切片允许省略起始/结束/步长) - 推荐用
key.indices(len(self._data))获取归一化后的整数三元组,它自动处理负索引、越界和None
slice.indices() 是安全切片的核心工具
slice.indices(length) 返回 (start, stop, step) 三元组,所有值都是非负整数且已裁剪到 [0, length] 范围内。不手动算,就容易在 mylist[-2:] 或 mylist[100:] 这类场景出错。
示例对比:
class MyList:
def __init__(self, data):
self._data = data
<pre class="brush:php;toolbar:false;">def __getitem__(self, key):
if isinstance(key, slice):
# ✅ 安全做法
start, stop, step = key.indices(len(self._data))
return [self._data[i] for i in range(start, stop, step)]
else:
# ❌ 错误示范:没处理 key 为负或越界
return self._data[key]
支持高级切片(如步长为负、空切片)的关键是信任 indices()
当用户写 obj[::-1] 或 obj[5:2:-1],slice.indices() 会返回合法的 range 参数;而手写逻辑很容易漏掉方向反转时的边界调整。
常见陷阱:
- 自己实现时用
range(key.start, key.stop, key.step)—— 但key.start可能是None或负数,直接传给range会报错 - 忽略空切片:比如
obj[3:1]在正向切片中本应返回空列表,但手动计算range(3,1)就是空的,看似没问题;可一旦加了负步长,逻辑就彻底乱套 -
indices()对空容器也安全:slice(10, 20).indices(0)返回(0, 0, 1),不会崩溃
自定义切片返回类型需与语义一致
如果原容器是可变序列(如模拟 list),切片应返回同类实例;如果是只读视图(如包装 NumPy 数组),返回新对象或代理都行。但别返回 list —— 这会让用户误以为修改返回值会影响原对象。
实操建议:
- 返回新实例时,确保构造函数接受数据序列(如
return self.__class__(result_data)) - 若底层是不可变结构(如
tuple),切片返回tuple更合理,避免诱导用户尝试.append() - 性能敏感场景下,可考虑返回惰性视图(如封装
itertools.islice),但必须文档注明“非即时求值”
真正容易被忽略的点是:切片行为必须和 Python 内建类型保持一致——不是“能跑就行”,而是 obj[::2] 和 obj[slice(None, None, 2)] 必须等价,且对任意合法 slice 输入都不抛意外异常。这要求你把 indices() 当成唯一可信的归一化入口,而不是自己拼逻辑。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











