Python字典存在对应Key却报KeyError?NetworkX AtlasView调试疑难
解析NetworkX AtlasView中诡异的KeyError问题
哇,这个问题真的太反直觉了——明明是同一个对象(id相同、is和==都返回True),却在字典里找不到,转成集合又能匹配上,简直让人摸不着头脑!先把你遇到的诡异现象再清晰列出来,方便大家理解:
>>> type(self._atlas) <class 'dict'> >>> type(key) <class 'SyntaxToken'> >>> self._atlas[key] Traceback (most recent call last): File "C:\Program Files\JetBrains\PyCharm Community Edition 2019.3.1\plugins\python-ce\helpers\pydev\_pydevd_bundle\pydevd_exec2.py", line 3, in Exec exec(exp, global_vars, local_vars) File "<input>", line 1, in <module> KeyError: при >>> list(self._atlas.keys())[0] is key True >>> list(self._atlas.keys())[0] == key True >>> key in self._atlas False >>> key in set(self._atlas.keys()) True >>> id(key), id(list(self._atlas.keys())[0]) (2487509812032, 2487509812032)
问题根源分析
出现这种矛盾现象的核心原因是字典的哈希表机制和SyntaxToken类的哈希实现不兼容,具体来说:
- Python字典的
in操作和__getitem__是先通过对象的哈希值定位到对应的桶,再在桶内比较对象是否相等; - 从
id相同可以确定这是同一个对象,但key in self._atlas返回False,说明当前对象的哈希值和它被存入字典时的哈希值不一样了; - 为什么转成集合就可以找到?因为
set(self._atlas.keys())会把字典里的键重新加入集合,这时候用的是对象当前的哈希值,而集合的in操作是基于当前哈希值来判断的,所以能匹配上。
导致哈希值变化的大概率原因是:
SyntaxToken类重写了__hash__方法,但该方法依赖了可变的实例属性;- 这个
key对象被存入字典后,它的某个可变属性被修改了,导致哈希值发生了变化; - 字典无法感知到哈希值的变化,仍然按照存入时的哈希值去查找,自然找不到对应的条目。
另外还有一种可能:如果SyntaxToken重写了__eq__但没有重写__hash__,不过这种情况通常在存入字典时就会抛出TypeError(因为默认__hash__会被设为None),所以你的情况更倾向于前者。
解决方案
针对这个问题,你可以按以下步骤修复:
检查
SyntaxToken的哈希实现:
确保__hash__方法只依赖对象的不可变属性(比如字符串、数字这类不可变类型),同时遵循Python规范:如果两个对象==相等,它们的__hash__必须返回相同的值。避免修改字典键的可变属性:
如果你必须使用SyntaxToken作为字典键,那么在将其存入字典后,绝对不要修改它的任何会影响哈希值的属性;最好把SyntaxToken设计成不可变类(比如用dataclasses.dataclass(frozen=True)来定义,或者手动限制属性修改)。临时应急方案:
如果暂时没法修改SyntaxToken的实现,可以通过遍历字典键的方式查找对应的条目(虽然效率低,但能解决当前问题):# 替代self._atlas[key]的查找方式 for k, v in self._atlas.items(): if k == key: value = v break else: raise KeyError(key)
内容的提问来源于stack exchange,提问作者nivedano
相关产品推荐
相关产品推荐

