Подтвердить что ты не робот

Хеширование словаря?

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

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

4b9b3361

Ответ 1

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

hash(frozenset(my_dict.items()))

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

ОБНОВЛЕНИЕ: Пожалуйста, смотрите комментарии ниже, почему этот подход не может дать стабильный результат.

Ответ 2

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

json.dumps(d, sort_keys=True)

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

Ответ 3

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

Хотя ответ принят, заголовок вопроса - "Хеширование словаря питона", и в отношении этого заголовка ответ неполон. (Что касается основной части вопроса, ответ завершен.)

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

Если кто-то ищет в Кару способ хэширования словаря, он может наткнуться на этот метко озаглавленный вопрос и оставить неудовлетворенным, если кто-то пытается хэшировать несколько вложенных словарей. Ответ выше не сработает в этом случае, и вам придется реализовать какой-то рекурсивный механизм для извлечения хеша.

Вот один из таких механизмов:

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

Ответ 4

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

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))  

Ответ 5

В приведенном ниже коде избегается использование функции хеша() 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=

Ответ 6

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

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

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

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

Ответ 7

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

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

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

Ответ 8

Вы могли бы использовать сторонний 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

Ответ 9

Общий подход хорош, но вы можете рассмотреть метод хэширования.

SHA был разработан для криптографической силы (скорость тоже, но сила важнее). Вы можете принять это во внимание. Поэтому использование встроенной функции hash, вероятно, является хорошей идеей, если только безопасность здесь не является ключом.

Ответ 10

Для этого вы можете использовать библиотеку maps. В частности, 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.

Ответ 11

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

hash(str(my_dict))