首页 新闻 会员 周边 捐助

自定义类如果用索引或者slice访问,只能在__getitem__()里面套用list等基本类型来取值吗,这不算是实现了__getitem__吧,不还是list自身取值吗,外面套了层壳

0
[已解决问题] 解决于 2025-09-05 11:16

自定义类如果用索引或者slice访问,只能在__getitem__()里面套用list等基本类型来取值吗,这不算是实现了__getitem__吧,不还是list自身取值吗,外面套了层壳

_java_python的主页 _java_python | 小虾三级 | 园豆:984
提问于:2025-09-05 11:13
< >
分享
最佳答案
0
__getitem__ 里把下标/切片转给内部 list 并返回”本身就是合法的、完整的协议实现——解释器只认你有没有 __getitem__,至于你是“真数组”还是“壳”它根本不关心。
只要你能把 obj[key] 映射到合理的返回值,就算你实现了协议,而不是“只能由基本类型来取值”。

  1. 协议只规定“能响应 []”,不规定“怎么存”

Python 数据模型文档对 __getitem__ 的要求只有一句话:
object.__getitem__(self, key)
Called to implement evaluation of self[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)   # 申请 5int 的空间
>>> 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__ 把它包装成“可以用 [] 访问”的对象,省内存、快,但只支持基础类型。

 

 
_java_python | 小虾三级 |园豆:984 | 2025-09-05 11:13
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册