Мне нужна структура, подобная словарю, которая может принимать нерасширяемые ключи и сопоставлять их со значением. Мне нужно это по двум причинам:
Проверка того, был ли элемент уже просмотрен в O (1) при перемещении списка
Сопоставление каждого элемента с идентификатором, например, символом
Созданная диктоподобная структура будет выбрасываться после процесса, поэтому ее нельзя использовать, если ключи могут быть изменены.
d = MutableKeyDict()
d[[1, 2, 3]] = 'a'
print([1, 2, 3] in d) # True
print((1, 2, 3) in d) # False
tl; dr я реализовал что-то, что не работает. Если вы видите канонический способ реализации этого, пропустите эту часть.
На данный момент я написал класс-оболочку, который реализует метод __hash__
который возвращается к неизменяемому типу, эквивалентному хэш-данным.
class ForcedHashable:
@staticmethod
def hashable(obj):
try:
hash(obj)
return obj
except TypeError:
if isinstance(obj, (list, tuple)):
return tuple(ForcedHashable.hashable(o) for o in obj)
elif isinstance(obj, set):
return frozenset(ForcedHashable(o) for o in obj)
elif isinstance(obj, dict):
return tuple((k, ForcedHashable.hashable(v)) for k, v in obj.items())
...
def __init__(self, data):
self.data = data
def __eq__(self, other):
return self.data == other.data
def __hash__(self):
return hash(self.hashable(self.data))
Это позволило мне написать проект пользовательского класса dict, который использует ForcedHashable
для ForcedHashable
своих ключей.
class MutableKeyDict(UserDict):
def __setitem__(self, key, value):
self.data[ForcedHashable(key)] = value
def __getitem__(self, item):
return self.data[ForcedHashable(item)]
def __contains__(self, item):
return ForcedHashable(item) in self.data
Он работает для основных случаев...
d = MutableKeyDict()
d[[1, 2, 3]] = 'a'
print([1, 2, 3] in d) # True
print((1, 2, 3) in d) # False
Но сталкивается с некоторыми проблемами с объектами, вложенными в себя.
d = MutableKeyDict()
x = []
x.append(x)
d[x] = 'foo' # raises a 'RecursionError: maximum recursion depth exceeded'
Разумеется, рекурсия исходит из этого утверждения:
if isinstance(obj, (list, tuple)):
return tuple(ForcedHashable.hashable(o) for o in obj)
Я был на полпути через внедрение исправления с memo
, вроде как тот, copy.deepcopy
используется copy.deepcopy
, но потом я понял, что даже если я это сделаю, этот метод также вызовет RecursionError
.
def __eq__(self, other):
return self.data == other.data
Я бы хотел, чтобы вышеупомянутое работало, по крайней мере, для встроенных типов.
Будет ли умный способ обойти этот RecursionError
? А если нет, существует ли канонический способ связать равные элементы (только встроенные типы) с временным хэшем? Другие подходы более чем приветствуются.
Нет причин, по deepcopy
техника deepcopy
не будет работать для решения проблемы рекурсии.
Я думаю, что вам может не deepcopy
это то, что deepcopy
основано на id
значений. Вам нужно только ловить объекты, которые содержат себя, идентично, а не объекты, которые содержат одинаковые, но разные объекты. В конце концов, вы не можете иметь бесконечную глубину различных, но равных объектов; это потребует бесконечной памяти.
На самом деле, вы можете сделать это проще, чем deepcopy
и pickle
, потому что на самом деле не имеет значения, что вы возвращаете для повторяющегося объекта, если только он hashable и уникальный. 1
Так, например:
def hashable(obj, *, memo=None):
if memo is None:
memo = set()
if id(obj) in memo:
return (..., id(obj))
memo.add(id(obj))
try:
hash(obj)
return obj
except TypeError:
if isinstance(obj, (list, tuple)):
return tuple(ForcedHashable.hashable(o, memo=memo) for o in obj)
elif isinstance(obj, set):
return frozenset(ForcedHashable(o, memo=memo) for o in obj)
elif isinstance(obj, dict):
return frozenset((k, ForcedHashable.hashable(v, memo=memo)) for k, v in obj.items())
raise
И сейчас:
>>> x = []
>>> x.append(x)
>>> ForcedHashable.hashable(x)
((Ellipsis, 4658316360),)
>>> d = MutableKeyDict()
>>> d[x] = d
>>> d[x]
{<__main__.ForcedHashable object at 0x115855240>: 2, <__main__.ForcedHashable object at 0x115a247f0>: {...}}
Пока мы это делаем, сделайте следующее:
elif isinstance(obj, (dict, MutableKeyDict)):
return frozenset((k, ForcedHashable.hashable(v, memo=memo)) for k, v in obj.items())
… и сейчас:
>>> d = MutableKeyDict()
>>> d[d] = d
>>> d
{<__main__.ForcedHashable object at 0x11584b320>: {...}}
1. Если вы не хотите, чтобы они работали как атомы Куайна, в этом случае вы хотите, чтобы он был хэшируемым и разделялся всеми другими атомами Куана того же типа, что так же просто.