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



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



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

485   9  

9 ответов:

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

hash(frozenset(my_dict.items()))

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

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

json.dumps(d, sort_keys=True)

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

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

хотя ответ был принят, название вопроса "хеширование словаря Python", и ответ является неполным в отношении этого наименования. (Что касается тела вопроса, ответ таков полный.)

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

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

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

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, чтобы окрошка по-разному в зависимости от его текущего определения, решение заключается в хэш все, что на самом деле меняется. В этом случае __дикт__ атрибутов:

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.х. Не тест в более ранних версиях, хотя я предполагаю, что make_hash() будет работать, скажем, 2.7.2. Что касается того, чтобы примеры работали, я do известно, что

func.__code__ 

следует заменить на

func.func_code

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

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

обновлено с 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

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

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

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

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

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

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

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

hash(str(my_dict))

Comments

    Ничего не найдено.