正如cpython 问题 88306中提出的,python WeakKeyDictionary对于不可散列类型失败。根据上面 python 问题中的讨论,这是一个不必要的限制,使用id键的 s 代替hash就可以了:在这种特殊情况下,ids 是 WeakKeyDictionary 中键的唯一标识符,因为当原始对象被删除。重要的是要注意,使用 ids 而不是哈希值仅在这种非常特殊的情况下才可行。
我们可以调整weakref.WeakKeyDictionary(参见要点)以实现所需的行为。总之,该实现将weakref键包装如下:
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)
# hold weak ref to avoid garbage collection of the remove callback
self._ref = weakref.ref(key, lambda _: remove(self))
def __call__(self):
# used in weakref.WeakKeyDictionary.__copy__
return self._ref()
def __repr__(self):
return f"<_IdKey(_id={self._id},{self._ref})>"
class WeakKeyIdDictionary(weakref.WeakKeyDictionary):
"""
overrides all methods involving dictionary access key
"""
... https://gist.github.com/barmettl/b198f0cf6c22047df77483e8aa28f408
Run Code Online (Sandbox Code Playgroud)
然而,这取决于weakref.WeakKeyDictionary(这里使用 python3.10)的实现细节,并且很可能在未来(甚至过去)版本的 python 中出现问题。当然,也可以重写一个全新的类。
也可以为所有类实现自定义__hash__方法,但这在处理外部代码时不起作用,并且会为 .NET 之外的用例提供不可靠的哈希值weakref.WeakKeyDictionary。我们还可以猴子补丁__hash__,但这尤其是对于内置类来说是不可能的,并且会对代码的其他部分产生意想不到的影响。
因此出现以下问题:应该如何在 WeakKeyDictionary 中存储不可散列的项?
有一种方法不依赖于了解 的内部结构WeakKeyDictionary:
from weakref import WeakKeyDictionary, WeakValueDictionary
class Id:
def __init__(self, key):
self._id = id(key)
def __hash__(self):
return self._id
def __eq__(self, other):
return self._id == other._id
class WeakUnhashableKeyDictionary:
def __init__(self, *args, **kwargs):
# TODO Do something to initialize given args and kwargs.
self.keys = WeakValueDictionary()
self.values = WeakKeyDictionary()
def __getitem__(self, key):
return self.values.__getitem__(Id(key))
def __setitem__(self, key, value):
_id = Id(key)
# NOTE This works because key holds on _id iif key exists,
# and _id holds on value iif _id exists. Transitivity. QED.
# Because key is only stored as a value, it does not need to be hashable.
self.keys.__setitem__(_id, key)
self.values.__setitem__(_id, value)
def __delitem__(self, key):
self.keys.__delitem__(Id(key))
self.values.__delitem__(Id(key))
# etc. other methods should be relatively simple to implement.
# TODO Might require some locks or care in the ordering of operations to work threaded.
# TODO Add clean error handling.
Run Code Online (Sandbox Code Playgroud)
这只是我对方法缓存问题的回答的概括。