Alb*_*ert 8 python weak-references
我想使用一些对象作为某些字典的键,这些对象要么是不可散列的,要么是可散列的,但我想用默认的__eq__/覆盖它们的/ ,即iff 。__hash__object.__eq__object.__hash__a == ba is b
(这些对象可能是numpy.ndarray、、torch.Tensor或其他东西,但我现在想问一般性的问题。)
例如:
x = numpy.array([2,3,4])
d = {x: 5}
Run Code Online (Sandbox Code Playgroud)
那就例外了TypeError: unhashable type: 'numpy.ndarray'。
或者:
x = torch.Tensor([2,3,4])
d = weakref.WeakKeyDictionary()
d[x] = 5
print(d[x])
Run Code Online (Sandbox Code Playgroud)
那就例外了RuntimeError: Boolean value of Tensor with more than one value is ambiguous。(这是相当误导或意外的。但我认为这是因为它会bool(__eq__(...))在内部执行。但是,奇怪的是,当我在这里使用法线时没有这样的异常dict。为什么?)
我可以编写一个自定义对象包装器来解决这个问题,例如:
class WrappedObject:
def __init__(self, orig):
self.orig = orig
def __eq__(self, other):
return object.__eq__(self.orig, other.orig)
def __ne__(self, other):
return object.__ne__(self.orig, other.orig)
def __hash__(self):
return object.__hash__(self.orig)
Run Code Online (Sandbox Code Playgroud)
这样就解决了第一种情况。现在我可以写:
x = numpy.array([2,3,4])
d = {WrappedObject(x): 5}
print(d[WrappedObject(x)]) # 5
Run Code Online (Sandbox Code Playgroud)
WrappedObject某些标准库中有类似的东西吗?
该id函数具有类似的行为,尽管它只返回int, 并且没有对原始对象的引用。所以对于这个例子,我可以写:
x = numpy.array([2,3,4])
d = {id(x): 5}
print(d[id(x)]) # 5
Run Code Online (Sandbox Code Playgroud)
请注意,这可能会出现问题!如果x稍后被释放,那么理论上可以创建另一个具有相同的对象id,因为id仅保证在生命周期内唯一,而不是在其生命周期之后。(相关问题在这里,尽管接受的答案确实有这个问题。)
使用 时不会发生此问题WrappedObject,因为引用始终保持活动状态。
是否有某种东西可以包装起来dict自动使用某种东西,就像WrappedObject在引擎盖下一样?也就是说,我特别希望对于所有键,它仅使用它们的身份来实现平等。
现在具体考虑我的第二个案例WeakKeyDictionary。我无法使用WrappedObject,因为它WrappedObject本身没有保持活动状态,所以所有键都会立即消失:
x = torch.Tensor([2,3,4])
d = weakref.WeakKeyDictionary()
d[WrappedObject(x)] = 5
print(list(d.items())) # prints []
Run Code Online (Sandbox Code Playgroud)
我目前看到的唯一真正的解决方案是重新实现WeakKeyDictionary自己,使用类似WrappedRefObject. 有更好的解决方案吗?这是否已经存在于标准库或其他地方?
使用 创建包装类可能不太困难__getattr__,尽管创建您自己的映射类可能更健壮,如下所示(未经测试):
import collections.abc
import weakref
class WeakKeyIdMap(collections.abc.MutableMapping):
"""Like weakref.WeakKeyDictionary except identity is used, not hash."""
def __init__(self):
self._values = {}
self._keyrefs = {}
def pop_id(self, idk):
self._keyrefs.pop(idk)
return self._values.pop(idk)
def __getitem__(self, key):
return self._values[id(key)]
def __setitem__(self, key, value):
idk = id(key)
if idk not in self._keyrefs:
self._keyrefs[idk] = weakref.ref(key, lambda: self.pop_id(idk))
self._values[idk] = value
def __delitem__(self, key):
idk = id(key)
self._keyrefs.pop(idk)
del self._values[idk]
def __iter__(self):
for idk in self._values: # Use self._values to preserve iteration order.
key = self._keyrefs[idk]()
if key is not None or key == id(None):
yield key
def __len__(self):
return len(self._values)
Run Code Online (Sandbox Code Playgroud)