自定义类如果用索引或者slice访问,只能在__getitem__()里面套用list等基本类型来取值吗,这不算是实现了__getitem__吧,不还是list自身取值吗,外面套了层壳
__getitem__
里把下标/切片转给内部 list 并返回”本身就是合法的、完整的协议实现——解释器只认你有没有 __getitem__
,至于你是“真数组”还是“壳”它根本不关心。obj[key]
映射到合理的返回值,就算你实现了协议,而不是“只能由基本类型来取值”。__getitem__
的要求只有一句话:object.__getitem__(self, key)
Called to implement evaluation ofself[key]
.
# 方案 A:纯委托 class MyList: def __init__(self, data): self._data = list(data) # 内部用 list def __getitem__(self, idx): return self._data[idx] # 直接转发 # 方案 B:自己算偏移 class MyArray: def __init__(self, size): self._buf = (ctypes.c_int * size)() def __getitem__(self, idx): return self._buf[idx] # 自己定位内存 对外部用户而言,都是 a[i]、a[1:5] 生效,协议已满足。 “壳”也能加价值——看需求 很多时候我们就是故意套壳,以便: 加日志/权限/缓存 def __getitem__(self, idx): logger.info('access %s', idx) return self._data[idx] 把负索引、切片自动映射到环形缓冲区、数据库分页、网络请求…… def __getitem__(self, idx): page, offset = divmod(idx, self.PAGE_SIZE) return self._fetch_page(page)[offset] 返回完全不同的类型(代理、视图、计算结果) def __getitem__(self, key): return ImageSlice(self, key) # 返回一个“切片对象”而非真实像素 这些都叫“实现了 __getitem__”,而且比裸 list 更强大。 什么时候需要“自己算” 只有当你想 节省内存(百万级对象不想存成 Python list) 做懒加载/分页/远程读取 支持稀疏、压缩、多维、不规则下标 才值得自己写算法;否则直接委托是最简单、最不容易出 bug 的做法。 Python 标准库本身就大量这么干——collections.UserList、deque、array.array 都是把下标/切片转给内部 C 数组或 list。 “套壳”不是作弊,而是协议允许的正常实现; 解释器只看你能不能响应 [],不关心你背后是真数组、假数组、还是远程 API。 只要 __getitem__ 返回了用户期望的值,你就成功实现了协议。 # 方案 B:自己算偏移 class MyArray: def __init__(self, size): self._buf = (ctypes.c_int * size)() def __getitem__(self, idx): return self._buf[idx] 这段代码没看懂,怎么用的 这段代码其实只有 3 步: 在堆里申请一块 真正的 C 级连续内存(像 C 的 int arr[size])。 当 Python 代码写下 a[idx] 时,自动把下标翻译成这块内存的偏移并返回对应值。 完全不需要 Python 的 list 对象,省内存、速度快,但只能存 C 的 int。 import ctypes # 提供“Python 到 C”的桥梁 class MyArray: def __init__(self, size): # 创建 C 数组:连续 size 个 int,全部初始化为 0 self._buf = (ctypes.c_int * size)() # 等价于 C: int arr[size] = {0}; def __getitem__(self, idx): # 直接返回 C 数组里第 idx 个 int return self._buf[idx] >>> arr = MyArray(5) # 申请 5 个 int 的空间 >>> arr[0] 0 >>> arr[3] = 999 # 支持赋值,因为 ctypes.c_int 是可变内存 >>> arr[3] 999 >>> arr[1:4] # 切片目前会抛 TypeError,需要再实现 __setitem__ 和 slice 支持 TypeError: slice not supported ctypes.c_int * size 就是在 Python 里“手动开一块原生 int 数组”, __getitem__ 把它包装成“可以用 [] 访问”的对象,省内存、快,但只支持基础类型。