子类化dict时get与__getitem__的差异及递归错误原因
这个问题踩中了Python字典特殊方法和普通方法的关键差异,我来一步步给你理清楚:
先搞懂get()和__getitem__的基础区别
- getitem:这是Python用来实现
[]索引访问的特殊方法。当你写d['a']的时候,Python底层直接调用的就是d.__getitem__('a')。dict父类的__getitem__逻辑是:找到key就返回值,找不到就检查子类有没有实现__missing__方法,有就调用它,没有就抛KeyError。 - get():这是dict类提供的普通实例方法,它的默认实现其实是内部调用
__getitem__,但会捕获KeyError——如果key不存在,就返回你指定的默认值(默认是None)。
分析你的两个子类案例
1. MyDict(重写get()没问题)
你写的get()方法里返回self[key],这里的self[key]会触发父类dict的__getitem__(因为你没重写__getitem__)。父类的__getitem__遇到不存在的key时,会自动调用你实现的__missing__返回0,整个流程是:d.get('c') → 调用自定义get() → self['c'] → 父类__getitem__ → 触发__missing__ → 返回0,完全没有递归。
2. MyDict2(重写__getitem__触发递归错误)
问题出在你重写的__getitem__里:return self[key]。当你调用d['a']或者d['c']时,Python会调用你这个自定义的__getitem__方法,而方法里的self[key]又会再次调用这个自定义的__getitem__,于是就陷入了无限循环:d['a'] → 自定义__getitem__ → self['a'] → 自定义__getitem__ → ... 直到Python达到递归深度上限,抛出RecursionError。
这里的核心是:重写__getitem__时,不能直接用self[key],因为那就是在调用你自己写的这个方法。如果要复用父类的逻辑,必须显式调用父类的__getitem__,比如用super().__getitem__(key)。
修复后的MyDict2应该是这样:
class MyDict2(dict): def __missing__(self, key): return 0 def __getitem__(self, key): # 调用父类的__getitem__,它会自动触发__missing__ return super().__getitem__(key)
这样当你访问d['c']时,父类的__getitem__找不到key,就会调用你实现的__missing__返回0,完美解决递归问题。
总结两者的关键差异
- 重写get()时,调用
self[key]会触发父类的__getitem__,和当前的get()方法是两个独立的函数,不会递归。 - 重写__getitem__时,
self[key]就是在调用当前的__getitem__方法,直接这么写必然导致无限递归,必须通过super()调用父类的实现来避免。
内容的提问来源于stack exchange,提问作者yoyoyoyo123

