用于不可散列类型的 Python WeakKeyDictionary

Pet*_*ler 7 python

正如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 中存储不可散列的项?

Xoe*_*Xoe 3

有一种方法不依赖于了解 的内部结构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)

这只是我对方法缓存问题的回答的概括。