Использование не-хэшируемых объектов Python в качестве ключей в словарях
Python не позволяет использовать не-хэшируемые объекты в качестве ключей в других словарях. Как отметил Андрей Власовских, есть хороший обходной путь для частного случая использования не вложенных словарей в качестве ключей:
frozenset(a.items())#Can be put in the dictionary instead
Есть ли способ использования произвольных объектов в качестве ключей в словарях?
Пример:
Как это будет использоваться в качестве ключа?
{"a":1, "b":{"c":10}}
Очень редко вам придется использовать что-то вроде этого в вашем коде. Если вы считаете, что это так, сначала попробуйте изменить модель данных.
Точный пример использования
Вариант использования - это кеширование вызовов функции произвольного ключевого слова. Каждый ключ в словаре - это строка (имя аргумента), и объекты могут быть довольно сложными, состоящими из слоистых словарей, списков, кортежей, ect.
Связанные проблемы
Эта суб-проблема была отколота от проблемы здесь. Решения здесь касаются случая, когда словари не являются слоистыми.
Ответы
Ответ 1
Основано на решении Криса Лутца. Обратите внимание, что это не обрабатывает объекты, которые изменяются путем итерации, такие как потоки, и не обрабатывает циклы.
import collections
def make_hashable(obj):
"""WARNING: This function only works on a limited subset of objects
Make a range of objects hashable.
Accepts embedded dictionaries, lists or tuples (including namedtuples)"""
if isinstance(obj, collections.Hashable):
#Fine to be hashed without any changes
return obj
elif isinstance(obj, collections.Mapping):
#Convert into a frozenset instead
items=list(obj.items())
for i, item in enumerate(items):
items[i]=make_hashable(item)
return frozenset(items)
elif isinstance(obj, collections.Iterable):
#Convert into a tuple instead
ret=[type(obj)]
for i, item in enumerate(obj):
ret.append(make_hashable(item))
return tuple(ret)
#Use the id of the object
return id(obj)
Ответ 2
не делать. Я согласен с комментарием Андрея на предыдущем вопросе, который не имеет смысла иметь словари как ключи, и особенно не вложенные. Ваша модель данных, очевидно, довольно сложна, и словари, вероятно, не являются правильным ответом. Вы должны попробовать немного OO.
Ответ 3
На основании решения Криса Лутца снова.
import collections
def hashable(obj):
if isinstance(obj, collections.Hashable):
items = obj
elif isinstance(obj, collections.Mapping):
items = frozenset((k, hashable(v)) for k, v in obj.iteritems())
elif isinstance(obj, collections.Iterable):
items = tuple(hashable(item) for item in obj)
else:
raise TypeError(type(obj))
return items
Ответ 4
Если вы действительно должны сделать свои объекты хешируемыми. Подкласс, который вы хотите добавить в качестве ключа, и предоставить функцию __hash__, которая возвращает уникальный ключ этому объекту.
Чтобы проиллюстрировать:
>>> ("a",).__hash__()
986073539
>>> {'a': 'b'}.__hash__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable
Если ваш хэш не является уникальным, вы получите столкновение. Может быть и медленным.
Ответ 5
С рекурсией!
def make_hashable(h):
items = h.items()
for item in items:
if type(items) == dict:
item = make_hashable(item)
return frozenset(items)
Вы можете добавить тесты другого типа для любых других изменяемых типов, которые вы хотите сделать hashable. Это не должно быть сложно.
Ответ 6
Я полностью не согласен с комментариями и ответами, говоря, что это не должно быть сделано для причины чистоты модели данных.
Словарь связывает объект с другим объектом, используя первый в качестве ключа. Словари не могут использоваться в качестве ключей, потому что они не хешируются. Это не делает менее значимым/практичным/необходимым для сопоставления словарей с другими объектами.
Поскольку я понимаю систему связывания Python, вы можете привязать любой словарь к ряду переменных (или наоборот, зависит от вашей терминологии), что означает, что эти переменные все знают один и тот же уникальный "указатель" на этот словарь. Нельзя ли использовать этот идентификатор как хэширующий ключ?
Если ваша модель данных обеспечивает/обеспечивает соблюдение того, что у вас не может быть двух словарей с тем же содержимым, что и ключи, это кажется мне безопасным методом.
Я должен добавить, что я понятия не имею, как это сделать/должно быть сделано, хотя.
Я не совсем понимаю, должен ли это быть ответ или комментарий. Пожалуйста, исправьте меня при необходимости.
Ответ 7
Я столкнулся с этой проблемой при использовании декоратора, который кэширует результаты предыдущих вызовов на основе сигнатуры вызова. Я не согласен с комментариями/ответами здесь о влиянии "вы не должны этого делать", но я думаю, что важно осознать потенциал для неожиданного и неожиданного поведения при движении по этому пути. Моя мысль состоит в том, что, поскольку экземпляры являются одновременно изменяемыми и хешируемыми, и, похоже, не представляется правдоподобным изменить это, нет ничего неправильного в создании хешируемых эквивалентов не-хешируемых типов или объектов. Но, конечно, это только мое мнение.
Для тех, кто требует совместимости с Python 2.5, может оказаться полезным нижеследующее. Я основывал его на более раннем ответе.
from itertools import imap
tuplemap = lambda f, data: tuple(imap(f, data))
def make_hashable(obj):
u"Returns a deep, non-destructive conversion of given object to an equivalent hashable object"
if isinstance(obj, list):
return tuplemap(make_hashable, iter(obj))
elif isinstance(obj, dict):
return frozenset(tuplemap(make_hashable, obj.iteritems()))
elif hasattr(obj, '__hash__') and callable(obj.__hash__):
try:
obj.__hash__()
except:
if hasattr(obj, '__iter__') and callable(obj.__iter__):
return tuplemap(make_hashable, iter(obj))
else:
raise NotImplementedError, 'object of type %s cannot be made hashable' % (type(obj),)
else:
return obj
elif hasattr(obj, '__iter__') and callable(obj.__iter__):
return tuplemap(make_hashable, iter(obj))
else:
raise NotImplementedError, 'object of type %s cannot be made hashable' % (type, obj)
Ответ 8
Я согласен с Леннартом Реджебро, что вы этого не делаете. Однако я часто считаю полезным кэшировать некоторые вызовы функций, вызываемый объект и/или объекты Flyweight, поскольку они могут использовать аргументы ключевых слов.
Но если вы действительно этого хотите, попробуйте pickle.dumps
(или cPickle
, если python 2.6) как быстрый и грязный взломать. Это намного быстрее, чем любой из ответов, в которых используются рекурсивные вызовы, чтобы сделать элементы неизменяемыми, а строки - хешируемыми.
import pickle
hashable_str = pickle.dumps(unhashable_object)