Para fins de armazenamento em cache, eu preciso gerar uma chave de cache a partir dos argumentos GET que estão presentes em um dict.
Atualmente estou usando sha1(repr(sorted(my_dict.items())))
( sha1()
é um método de conveniência que usa hashlib internamente), mas estou curioso para saber se existe uma maneira melhor.
python
hash
dictionary
ThiefMaster
fonte
fonte
{'a': 1, 'b':2}
é semanticamente o mesmo que{'b':2, 'a':1}
). Eu não o usei em nada muito complicado ainda, então YMMV, mas feedback é bem-vindo.Respostas:
Se o seu dicionário não estiver aninhado, você poderá criar um frozenset com os itens do dict e usar
hash()
:Isso é muito menos computacionalmente intenso do que gerar a sequência JSON ou representação do dicionário.
ATUALIZAÇÃO: Por favor, veja os comentários abaixo, por que essa abordagem pode não produzir um resultado estável.
fonte
hash()
função não produza uma saída estável. Isso significa que, com a mesma entrada, ele retorna resultados diferentes com instâncias diferentes do mesmo interpretador python. Para mim, parece que algum tipo de valor inicial é gerado toda vez que o intérprete é iniciado.Usar
sorted(d.items())
não é suficiente para obter um repr estável. Alguns dos valores tambémd
podem ser dicionários, e suas chaves ainda serão exibidas em uma ordem arbitrária. Contanto que todas as chaves sejam strings, eu prefiro usar:Dito isto, se os hashes precisarem ser estáveis em diferentes máquinas ou versões do Python, não tenho certeza de que isso seja à prova de balas. Você pode adicionar os argumentos
separators
eensure_ascii
para se proteger de quaisquer alterações nos padrões lá. Eu apreciaria comentários.fonte
ensure_ascii
argumento protegeria contra esse problema inteiramente hipotético.make_hash
. gist.github.com/charlax/b8731de51d2ea86c6eb9default=str
aodumps
comando. Parece funcionar bem.EDIT : Se todas as suas chaves são strings , então antes de continuar a ler esta resposta, consulte significativamente de Jack O'Connor solução mais simples (e mais rápido) (que também trabalha para hash dicionários aninhados).
Embora uma resposta tenha sido aceita, o título da pergunta é "Hashing um dicionário python" e a resposta está incompleta em relação a esse título. (No que diz respeito ao corpo da pergunta, a resposta está completa.)
Dicionários aninhados
Se alguém pesquisar Stack Overflow para saber como fazer hash em um dicionário, poderá se deparar com essa pergunta com título adequado e ficar insatisfeito se estiver tentando hash multiplicar dicionários aninhados. A resposta acima não funcionará neste caso, e você precisará implementar algum tipo de mecanismo recursivo para recuperar o hash.
Aqui está um desses mecanismos:
Bônus: Hashing de objetos e classes
A
hash()
função funciona muito bem quando você hash classes ou instâncias. No entanto, aqui está um problema que encontrei com o hash, no que diz respeito aos objetos:O hash é o mesmo, mesmo depois que eu mudei de foo. Isso ocorre porque a identidade de foo não mudou, então o hash é o mesmo. Se você deseja que o hash seja diferente de acordo com sua definição atual, a solução é fazer o hash do que está realmente mudando. Nesse caso, o
__dict__
atributo:Infelizmente, quando você tenta fazer o mesmo com a própria classe:
A
__dict__
propriedade da classe não é um dicionário normal:Aqui está um mecanismo semelhante ao anterior, que manipulará as classes adequadamente:
Você pode usar isso para retornar uma tupla de hash de quantos elementos você desejar:
NOTA: todo o código acima assume o Python 3.x. Não testou em versões anteriores, embora eu assuma
make_hash()
que funcionará, digamos, 2.7.2. Tanto quanto fazer a exemplos de trabalho, eu não sei quedeve ser substituído por
fonte
hash
listas e tuplas. Caso contrário, ele pega minhas listas de números inteiros que são valores no meu dicionário e retorna listas de hashes, que não é o que eu quero.Aqui está uma solução mais clara.
fonte
if isinstance(o,list):
paraif isinstance(obj, (set, tuple, list)):
, esta função poderá funcionar em qualquer objeto.O código abaixo evita o uso da função hash () do Python porque não fornecerá hashes consistentes nas reinicializações do Python (consulte a função hash no Python 3.3 retorna resultados diferentes entre as sessões ).
make_hashable()
converterá o objeto em tuplas aninhadas emake_hash_sha256()
também converterárepr()
em um hash SHA256 codificado em base64.fonte
make_hash_sha256(((0,1),(2,3)))==make_hash_sha256({0:1,2:3})==make_hash_sha256({2:3,0:1})!=make_hash_sha256(((2,3),(0,1)))
. Essa não é a solução que estou procurando, mas é um bom intermediário. Estou pensando em adicionartype(o).__name__
ao início de cada uma das tuplas para forçar a diferenciação.tuple(sorted((make_hashable(e) for e in o)))
Atualizado a partir da resposta de 2013 ...
Nenhuma das respostas acima me parece confiável. O motivo é o uso de itens (). Tanto quanto eu sei, isso sai em uma ordem dependente da máquina.
Que tal isso?
fonte
dict.items
não retornar uma lista previsível?frozenset
cuida dissohash
interna não se importa com a forma como o conteúdo do frozenset é impresso ou algo parecido. Teste-o em várias máquinas e versões em python e você verá.Para preservar a ordem das chaves, em vez de
hash(str(dictionary))
ouhash(json.dumps(dictionary))
prefiro uma solução rápida e suja:Funcionará mesmo para tipos como
DateTime
e mais que não são JSON serializáveis.fonte
Você pode usar o
frozendict
módulo de terceiros para congelar seu ditado e torná-lo lavável.Para manipular objetos aninhados, você pode usar:
Se você deseja dar suporte a mais tipos, use
functools.singledispatch
(Python 3.7):fonte
dict
deDataFrame
objetos.elif
cláusula para que ele funcione comDataFrame
s:elif isinstance(x, pd.DataFrame): return make_hashable(hash_pandas_object(x).tolist())
eu vou editar a resposta e ver se você aceitá-lo ...hash
randomization é um recurso de segurança deliberado ativado por padrão no python 3.7.Você pode usar a biblioteca de mapas para fazer isso. Especificamente, maps.FrozenMap
Para instalar
maps
, basta fazer:Ele também lida com o
dict
caso aninhado :Disclaimer: Eu sou o autor da
maps
biblioteca.fonte
.recurse
. Consulte maps.readthedocs.io/en/latest/api.html#maps.FrozenMap.recurse . A ordenação em listas é semanticamente significativa. Se você deseja independência de ordem, pode converter suas listas em conjuntos antes da chamada.recurse
. Você também pode fazer uso dolist_fn
parâmetro para.recurse
usar uma estrutura de dados Hashable diferentetuple
(.egfrozenset
)Uma maneira de abordar o problema é fazer uma tupla dos itens do dicionário:
fonte
Eu faço assim:
fonte
hash(str({'a': 1, 'b': 2})) != hash(str({'b': 2, 'a': 1}))
(embora possa funcionar para alguns dicionários, não é garantido que funcione em todos).