如何解决Python WeakKeyDictionary无法存储不可哈希类型的问题?
如何在WeakKeyDictionary中存储不可哈希类型?
正如cpython issue 88306中指出的,Python的weakref.WeakKeyDictionary无法处理不可哈希类型。相关讨论认为这是不必要的限制——在WeakKeyDictionary这个特殊场景下,用对象的id而非哈希值作为键的唯一标识完全可行,因为原对象被垃圾回收时,对应的键会自动从字典中移除。
解决方案:自定义WeakKeyIdDictionary
我们可以通过继承weakref.WeakKeyDictionary并修改键的处理逻辑来实现需求,核心是用两个包装类来基于对象id生成可哈希的弱引用键:
import weakref import typing import typing_extensions class _IdKey: def __init__(self, key): self._id = id(key) def __hash__(self): return self._id def __eq__(self, other: typing_extensions.Self): return self._id == other._id def __repr__(self): return f"<_IdKey(_id={self._id})>" class _IdWeakRef(_IdKey): def __init__(self, key, remove: typing.Callable[[typing.Any], None]): super().__init__(key) # 持有弱引用,避免移除回调被垃圾回收 self._ref = weakref.ref(key, lambda _: remove(self)) def __call__(self): # 供weakref.WeakKeyDictionary.__copy__调用 return self._ref() def __repr__(self): return f"<_IdKey(_id={self._id},{self._ref})>" class WeakKeyIdDictionary(weakref.WeakKeyDictionary): """ 重写所有涉及字典键访问的方法,用_id作为哈希依据 """ # 核心逻辑是将传入的键包装为_IdKey/_IdWeakRef实例后再操作底层字典 # 需补充实现__getitem__、__setitem__、__contains__等方法
需要注意的是,这个实现依赖Python 3.10版本WeakKeyDictionary的内部细节,在其他Python版本中可能失效。如果需要跨版本兼容,建议完全重写一个全新的类,而非继承官方实现。
不可行的方案说明
- 为目标类实现自定义
__hash__方法:这种方式对外部代码无效,且在WeakKeyDictionary以外的场景中,基于id的哈希逻辑并不可靠。 - 猴子补丁修改
__hash__:对Python内置类无法生效,还会影响其他依赖该类哈希逻辑的代码,引发意外问题。
内容的提问来源于stack exchange,提问作者Peter Barmettler
相关产品推荐
相关产品推荐

