__setitem__必须与__getitem__成对实现才能模拟字典行为,单独实现仅支持赋值但无法读取;推荐用dict作底层存储,并配套实现__delitem__和__contains__,校验须在赋值前完成。

__setitem__ 必须配合 __getitem__ 才能模拟字典行为
单独实现 __setitem__ 只能让对象支持 a[key] = value 赋值,但无法读取——a[key] 会直接报 TypeError: 'MyDict' object is not subscriptable。必须同时定义 __getitem__,否则连基本的键访问都失败。
常见错误是只写 __setitem__ 就以为“实现了字典访问”,结果一读就崩。建议一开始就成对实现:
def __getitem__(self, key):
return self._data[key] # 或抛 KeyError
<p>def <strong>setitem</strong>(self, key, value):
self._data[key] = value用 dict 作为底层存储最安全,别手写键值逻辑
自己维护键列表、做类型检查、处理哈希冲突……纯属给自己加戏。99% 的场景,直接用 dict 当内部容器就行,既快又稳。
容易踩的坑包括:
立即学习“Python免费学习笔记(深入)”;
- 用
list存键值对([(k,v),...]),导致__getitem__变成 O(n) 查找 - 在
__setitem__里漏掉对None、不可哈希类型(如list)的校验,结果运行时报TypeError: unhashable type - 没考虑
__delitem__和__contains__,导致del a['x']或'x' in a报错
推荐起步结构:
class MyDict:
def __init__(self):
self._data = {} # 别绕弯
<pre class="brush:php;toolbar:false;"><pre class="brush:php;toolbar:false;">def __getitem__(self, key):
return self._data[key]
def __setitem__(self, key, value):
self._data[key] = value
def __delitem__(self, key):
del self._data[key]
def __contains__(self, key):
return key in self._data</code>__setitem__ 里做数据校验要早,别等存完再清理
如果业务要求键必须是字符串、值必须是数字,校验必须放在赋值前。否则可能先写入非法值,再抛异常,导致对象处于不一致状态。
典型场景比如配置类:
- 键名只能是
'host'、<code>'port'、'timeout'—— 在__setitem__开头用if key not in {'host', 'port', 'timeout'}:拦住 - 值为
timeout时必须是正整数 —— 紧接着检查isinstance(value, int) and value > 0 - 别用
try/except包裹整个赋值再回滚,Python 没事务,回滚靠你自己,容易漏
示例片段:
def __setitem__(self, key, value):
if key == 'timeout':
if not isinstance(value, int) or value <= 0:
raise ValueError("timeout must be a positive integer")
elif key == 'host':
if not isinstance(value, str) or not value.strip():
raise ValueError("host must be a non-empty string")
self._data[key] = value继承 collections.abc.MutableMapping 是更省心的选择
手动补全 __len__、__iter__、keys()、values()、items() 很枯燥,还容易漏。直接继承 collections.abc.MutableMapping,它会基于你实现的 __getitem__、__setitem__、__delitem__、__iter__、__len__ 自动提供其余方法。
注意两点:
- 必须实现全部五个抽象方法,少一个就会实例化失败(
TypeError: Can't instantiate abstract class ...) -
__iter__返回键的迭代器(iter(self._data)),不是键值对;__len__返回len(self._data)
这样写之后,list(a.keys())、a.update({...}) 全都能用,不用自己造轮子。
真正麻烦的从来不是 __setitem__ 本身,而是配套的边界检查、一致性维护和协议完整性。很多人卡在“能赋值了”,却忘了“能查、能删、能遍历、能校验”才是完整闭环。


















