РЕДАКТИРОВАТЬ : Если все ваши ключи являются строками , то прежде чем продолжить читать этот ответ, пожалуйста, посмотрите значительно более простое (и более быстрое) решение Джека О'Коннора (которое также работает для хэширования вложенных словарей).
Хотя ответ принят, заголовок вопроса - «Хеширование словаря питона», и в отношении этого заголовка ответ неполон. (Что касается основной части вопроса, ответ полный.)
Вложенные словари
Если кто-то ищет в Stack Overflow способ хэширования словаря, он может наткнуться на этот метко озаглавленный вопрос и оставить неудовлетворенным, если кто-то пытается хэшировать несколько вложенных словарей. Ответ выше не сработает в этом случае, и вам придется реализовать какой-то рекурсивный механизм для извлечения хеша.
Вот один из таких механизмов:
import copy
def make_hash(o):
"""
Makes a hash from a dictionary, list, tuple or set to any level, that contains
only other hashable types (including any lists, tuples, sets, and
dictionaries).
"""
if isinstance(o, (set, tuple, list)):
return tuple([make_hash(e) for e in o])
elif not isinstance(o, dict):
return hash(o)
new_o = copy.deepcopy(o)
for k, v in new_o.items():
new_o[k] = make_hash(v)
return hash(tuple(frozenset(sorted(new_o.items()))))
Бонус: хеширование объектов и классов
hash()
Функция отлично работает , когда вы хэширования классы или экземпляры. Тем не менее, вот одна проблема, которую я нашел с хэшем, что касается объектов:
class Foo(object): pass
foo = Foo()
print (hash(foo)) # 1209812346789
foo.a = 1
print (hash(foo)) # 1209812346789
Хэш остается тем же, даже после того, как я изменил foo. Это связано с тем, что идентичность foo не изменилась, поэтому хеш остается тем же. Если вы хотите, чтобы foo хэшировал по-разному в зависимости от его текущего определения, решение состоит в том, чтобы хэшировать все, что на самом деле меняется. В этом случае __dict__
атрибут:
class Foo(object): pass
foo = Foo()
print (make_hash(foo.__dict__)) # 1209812346789
foo.a = 1
print (make_hash(foo.__dict__)) # -78956430974785
Увы, когда вы пытаетесь сделать то же самое с самим классом:
print (make_hash(Foo.__dict__)) # TypeError: unhashable type: 'dict_proxy'
Свойство класса __dict__
не является обычным словарем:
print (type(Foo.__dict__)) # type <'dict_proxy'>
Вот механизм, аналогичный предыдущему, который будет обрабатывать классы соответствующим образом:
import copy
DictProxyType = type(object.__dict__)
def make_hash(o):
"""
Makes a hash from a dictionary, list, tuple or set to any level, that
contains only other hashable types (including any lists, tuples, sets, and
dictionaries). In the case where other kinds of objects (like classes) need
to be hashed, pass in a collection of object attributes that are pertinent.
For example, a class can be hashed in this fashion:
make_hash([cls.__dict__, cls.__name__])
A function can be hashed like so:
make_hash([fn.__dict__, fn.__code__])
"""
if type(o) == DictProxyType:
o2 = {}
for k, v in o.items():
if not k.startswith("__"):
o2[k] = v
o = o2
if isinstance(o, (set, tuple, list)):
return tuple([make_hash(e) for e in o])
elif not isinstance(o, dict):
return hash(o)
new_o = copy.deepcopy(o)
for k, v in new_o.items():
new_o[k] = make_hash(v)
return hash(tuple(frozenset(sorted(new_o.items()))))
Вы можете использовать это, чтобы вернуть кортеж хэша, сколько угодно элементов:
# -7666086133114527897
print (make_hash(func.__code__))
# (-7666086133114527897, 3527539)
print (make_hash([func.__code__, func.__dict__]))
# (-7666086133114527897, 3527539, -509551383349783210)
print (make_hash([func.__code__, func.__dict__, func.__name__]))
ПРИМЕЧАНИЕ: весь приведенный выше код предполагает использование Python 3.x. Не тестировал в более ранних версиях, хотя я предполагаю, что make_hash()
будет работать, скажем, в 2.7.2. Что касается делает работу примеров, я действительно знаю , что
func.__code__
следует заменить на
func.func_code