直接继承list不算真正“自定义容器”,因为它未显式实现容器协议要求的__len__和__getitem__等方法,无法控制切片返回类型、拦截负索引或注入校验逻辑;必须手动实现这两个核心方法才能被len()、索引和切片等内置操作正确识别和调用。

为什么直接继承 list 不能算“自定义容器”
很多人第一反应是 class MyList(list): pass,但这只是包装,不是实现。Python 的容器协议(container protocol)要求你显式支持 __len__、__getitem__ 等方法,才能被内置函数和语法(如 len()、[1:5])正确识别和调用。继承 list 虽然能用,但掩盖了底层行为,比如你无法控制切片返回类型、无法拦截负索引逻辑、也无法在 len() 调用时插入校验或缓存逻辑。
必须实现的两个核心方法:__len__ 和 __getitem__
要让实例响应 len(obj) 和 obj[...](包括切片),至少得实现:
-
__len__(self):返回非负整数。注意不能返回 float 或 None,否则len()会报TypeError: 'float' object cannot be interpreted as an integer -
__getitem__(self, key):key 可能是int(单个索引)或slice对象(如slice(1, 5, 2))。必须自己判断并分别处理,不能只写self._data[key]就完事——因为_data可能不支持切片(比如是 dict 或生成器)
示例片段:
myobj[1:4]
- 如果内部用
list存储,切片应返回self.__class__(...)实例,而非list(...) - 如果内部是不可变结构(如 tuple),构造新实例时需确保构造函数接受可迭代对象
- 若切片逻辑复杂(比如带过滤或映射),更要主动控制返回类型,避免意外暴露底层数据结构
容易被忽略的边界:负索引、空切片、step=0
slice.indices(n) 是关键工具,它自动把 [::2]、[-3:]、[100:200] 这类表达式归一为合法的 (start, stop, step) 元组。不调用它,自己手动算负索引很容易出错:
- 直接用
key.start or 0忽略了None在切片中的语义(表示“从头/到尾”) - 没处理
step=0会导致ValueError: slice step cannot be zero,但这个错误应由slice.indices()抛出,不是你手动检查 - 空切片(如
[5:3])经.indices(n)后会得到(5, 3, 1),遍历时自然不执行,无需额外判断
所以务必依赖 key.indices(len(self)),别自己重造轮子。
真正麻烦的是当你的容器逻辑本身不连续(比如稀疏数组、懒加载序列),这时 __len__ 和 __getitem__ 的语义需要严格对齐——长度不能是“已加载项数”,而应是“逻辑总长”。这点一旦错位,切片和循环就会行为异常。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











