火柴人武林大会
156.74M · 2026-02-04
写 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 个高频进阶场景
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,因为切片没实现(可再扩展)
class AutoDict(dict):
def __missing__(self, key):
self[key] = []
return self[key]
d = AutoDict()
d['users'].append('kimi') # 不会 KeyError
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 被当成越界。__delitem__ 里把最后一个元素删光后,记得同步计数器,否则 len() 不一致。五、一行总结
集齐 __xxitem__ 七龙珠,你的自定义对象就能在语义、性能、习惯上与 Python 原生容器“无感”互换;下次面试官问“如何让自己写的类支持切片?”直接把这篇链接甩给他。