写 Python 这么久,你有没有想过:
obj[1], obj['name'], obj[1:10:2], del obj[key]
这些语法为什么对 list、dict 有效,对自己写的类却报错?
答案藏在 7 个以 item 为核心的“魔术方法”里。今天 10 分钟带你一次配齐,写完就能让老板/面试官眼前一亮。


一、先记住一张“七龙珠”表

魔术方法触发语法用途不写的后果
__getitem__obj[key] / 切片读元素直接 TypeError
__setitem__obj[key]=val写元素无法赋值
__delitem__del obj[key]删元素无法删除
__len__len(obj)容器长度list() 无限递归
__iter__for i in obj:迭代list() 无限递归
__contains__key in obj成员测试退回到暴力遍历
__missing__*dict[key]缺失兜底仅 dict 子类有效

二、最小可运行模板(支持切片、负数索引、越界提示)
把下面 30 行代码粘到 IDE,直接跑通:

class MyList:
    def __init__(self, init=()):
        self._data = list(init)

    # ---- 读 ----
    def __getitem__(self, idx):
        if isinstance(idx, slice):
            return self._data[idx]
        if -len(self._data) <= idx < len(self._data):
            return self._data[idx]
        raise IndexError('MyList index out of range')

    # ---- 写 ----
    def __setitem__(self, idx, value):
        if isinstance(idx, slice):
            self._data[idx] = value
            return
        if -len(self._data) <= idx < len(self._data):
            self._data[idx] = value
            return
        raise IndexError('MyList assignment index out of range')

    # ---- 删 ----
    def __delitem__(self, idx):
        if isinstance(idx, slice):
            del self._data[idx]
            return
        if -len(self._data) <= idx < len(self._data):
            del self._data[idx]
            return
        raise IndexError('MyList deletion index out of range')

    # ---- 长度 + 迭代 ----
    def __len__(self):
        return len(self._data)

    def __iter__(self):
        return iter(self._data)

    # ---- 调试友好 ----
    def __repr__(self):
        return f'{self.__class__.__name__}({self._data})'

测试一把:

m = MyList(range(5))
print(m)          # MyList([0, 1, 2, 3, 4])
print(m[1::2])    # [1, 3]
m[2] = 99
del m[-1]
print(m, len(m))  # MyList([0, 1, 99, 3]) 4

三、3 个高频进阶场景

  1. 同时支持“序列”+“映射”风格
class Row:
    def __init__(self, values, headers):
        self._row = values
        self._dict = dict(zip(headers, values))
    
    # 展示接口
    def __getitem__(self, key):
        if isinstance(key, int):
            return self._row[key]
        if isinstance(key, str):
            return self._dict[key]
        raise TypeError('key must be int or str')

    def __repr__(self):
        return f'Row({self._dict})'

# 使用示例
headers = ['id', 'name', 'age']
values  = [1, 'kimi', 18]
row = Row(values, headers)

print(row[0])      # 1      ← 把行当列表
print(row['name']) # 'kimi' ← 把行当字典
print(row[1:])     # 抛 TypeError,因为切片没实现(可再扩展)
  1. 让 dict 子类在 key 缺失时自动插入默认值
class AutoDict(dict):
    def __missing__(self, key):
        self[key] = []
        return self[key]

d = AutoDict()
d['users'].append('kimi')   # 不会 KeyError
  1. 多维数组下标 matrix[1, 2]
    key 当成 tuple 解包即可:
def __getitem__(self, key):
    if isinstance(key, tuple) and len(key) == 2:
        r, c = key
        return self._data[r][c]
    ...

四、那些年我们踩过的坑

  • 只写 __getitem__ 却忘了 __len____iter__list(myobj) 会无限递归直到 RecursionError。
  • __setitem__ 里忘记对负数索引做“转正”处理,-1 被当成越界。
  • 切片赋值左右长度不一致时,list 会自动伸缩;若想“定长”容器,需要主动拦截。
  • __delitem__ 里把最后一个元素删光后,记得同步计数器,否则 len() 不一致。

五、一行总结
集齐 __xxitem__ 七龙珠,你的自定义对象就能在语义、性能、习惯上与 Python 原生容器“无感”互换;下次面试官问“如何让自己写的类支持切片?”直接把这篇链接甩给他。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com