Хеширование словаря? - PullRequest
130 голосов
/ 04 мая 2011

Для целей кэширования мне нужно сгенерировать ключ кеша из аргументов GET, которые присутствуют в dict.

В настоящее время я использую sha1(repr(sorted(my_dict.items()))) (sha1() - это удобный метод, который использует hashlib для внутреннего использования)но мне любопытно, есть ли лучший способ.

Ответы [ 11 ]

110 голосов
/ 25 февраля 2014

Использование sorted(d.items()) недостаточно, чтобы получить стабильный ответ. Некоторые значения в d также могут быть словарями, и их ключи будут по-прежнему отображаться в произвольном порядке. Пока все ключи являются строками, я предпочитаю использовать:

json.dumps(d, sort_keys=True)

Тем не менее, если хэши должны быть стабильными на разных машинах или версиях Python, я не уверен, что это пуленепробиваемый. Возможно, вы захотите добавить аргументы separators и ensure_ascii, чтобы защитить себя от любых изменений настроек по умолчанию. Буду признателен за комментарии.

95 голосов
/ 04 мая 2011

Если ваш словарь не является вложенным, вы можете создать морозильник с элементами dict и использовать hash():

hash(frozenset(my_dict.items()))

Это намного менее вычислительно, чем генерирование JSONстрока или представление словаря.

57 голосов
/ 03 января 2012

РЕДАКТИРОВАТЬ : Если все ваши ключи строковые , то прежде чем продолжить читать этот ответ, пожалуйста, посмотрите значительно более простое (и более быстрое) решение Джека О'Коннора 1006 * (который также работает для хэширования вложенных словарей).

Несмотря на то, что ответ был принят, заголовок вопроса - «Хеширование словаря питона», и в отношении этого заголовка ответ неполон. (Что касается основной части вопроса, ответ завершен.)

Вложенные словари

Если кто-то ищет в 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'

Свойство class __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
11 голосов
/ 07 февраля 2014

Вот более четкое решение.

def freeze(o):
  if isinstance(o,dict):
    return frozenset({ k:freeze(v) for k,v in o.items()}.items())

  if isinstance(o,list):
    return tuple([freeze(v) for v in o])

  return o


def make_hash(o):
    """
    makes a hash out of anything that contains only list,dict and hashable types including string and numeric types
    """
    return hash(freeze(o))  
6 голосов
/ 10 февраля 2017

Приведенный ниже код избегает использования хэш-функции Python, поскольку она не предоставляет хэши, которые являются согласованными при перезапусках Python (см. хеш-функция в Python 3.3 возвращает разные результаты между сеансами ). make_hashable() преобразует объект во вложенные кортежи, а make_hash_sha256() также преобразует repr() в хэш SHA256 в кодировке base64.

import hashlib
import base64

def make_hash_sha256(o):
    hasher = hashlib.sha256()
    hasher.update(repr(make_hashable(o)).encode())
    return base64.b64encode(hasher.digest()).decode()

def make_hashable(o):
    if isinstance(o, (tuple, list)):
        return tuple((make_hashable(e) for e in o))

    if isinstance(o, dict):
        return tuple(sorted((k,make_hashable(v)) for k,v in o.items()))

    if isinstance(o, (set, frozenset)):
        return tuple(sorted(make_hashable(e) for e in o))

    return o

o = dict(x=1,b=2,c=[3,4,5],d={6,7})
print(make_hashable(o))
# (('b', 2), ('c', (3, 4, 5)), ('d', (6, 7)), ('x', 1))

print(make_hash_sha256(o))
# fyt/gK6D24H9Ugexw+g3lbqnKZ0JAcgtNW+rXIDeU2Y=
5 голосов
/ 04 марта 2013

Обновлено с 2013 года ...

Ни один из приведенных выше ответов не кажется мне надежным. Причина в использовании предметов (). Насколько я знаю, это происходит в машинно-зависимом порядке.

А как насчет этого?

import hashlib

def dict_hash(the_dict, *ignore):
    if ignore:  # Sometimes you don't care about some items
        interesting = the_dict.copy()
        for item in ignore:
            if item in interesting:
                interesting.pop(item)
        the_dict = interesting
    result = hashlib.sha1(
        '%s' % sorted(the_dict.items())
    ).hexdigest()
    return result
4 голосов
/ 30 января 2015

Для сохранения порядка ключей вместо hash(str(dictionary)) или hash(json.dumps(dictionary)) я бы предпочел быстрое и грязное решение:

from pprint import pformat
h = hash(pformat(dictionary))

Это будет работать даже для таких типов, как DateTime и более, которые не сериализуемы в JSON.

3 голосов
/ 28 октября 2018

Вы можете использовать сторонний модуль frozendict , чтобы заморозить ваш диктовку и сделать его хэш.

from frozendict import frozendict
my_dict = frozendict(my_dict)

Для обработки вложенных объектов вы можете использовать:

import collections.abc

def make_hashable(x):
    if isinstance(x, collections.abc.Hashable):
        return x
    elif isinstance(x, collections.abc.Sequence):
        return tuple(make_hashable(xi) for xi in x)
    elif isinstance(x, collections.abc.Set):
        return frozenset(make_hashable(xi) for xi in x)
    elif isinstance(x, collections.abc.Mapping):
        return frozendict({k: make_hashable(v) for k, v in x.items()})
    else:
        raise TypeError("Don't know how to make {} objects hashable".format(type(x).__name__))

Если вы хотите поддерживать больше типов, используйте functools.singledispatch (Python 3.7):

@functools.singledispatch
def make_hashable(x):
    raise TypeError("Don't know how to make {} objects hashable".format(type(x).__name__))

@make_hashable.register
def _(x: collections.abc.Hashable):
    return x

@make_hashable.register
def _(x: collections.abc.Sequence):
    return tuple(make_hashable(xi) for xi in x)

@make_hashable.register
def _(x: collections.abc.Set):
    return frozenset(make_hashable(xi) for xi in x)

@make_hashable.register
def _(x: collections.abc.Mapping):
    return frozendict({k: make_hashable(v) for k, v in x.items()})

# add your own types here
0 голосов
/ 08 ноября 2018

Вы можете использовать библиотеку maps для этого. В частности, карты. FrozenMap

import maps
fm = maps.FrozenMap(my_dict)
hash(fm)

Чтобы установить maps, просто выполните:

pip install maps

Он также обрабатывает вложенный случай dict:

import maps
fm = maps.FrozenMap.recurse(my_dict)
hash(fm)

Отказ от ответственности: я являюсь автором библиотеки maps.

0 голосов
/ 27 декабря 2014

Я делаю это так:

hash(str(my_dict))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...