Für Caching-Zwecke muss ich einen Cache-Schlüssel aus GET-Argumenten generieren, die in einem Diktat vorhanden sind.
Derzeit verwende ich sha1(repr(sorted(my_dict.items())))
( sha1()
ist eine bequeme Methode, die Hashhlib intern verwendet), aber ich bin gespannt, ob es einen besseren Weg gibt.
python
hash
dictionary
DiebMaster
quelle
quelle
{'a': 1, 'b':2}
ist semantisch das gleiche wie{'b':2, 'a':1}
). Ich habe es noch nicht für etwas zu Kompliziertes verwendet, also YMMV, aber Feedback ist willkommen.Antworten:
Wenn Ihr Wörterbuch nicht verschachtelt ist, können Sie ein Frozenset mit den Elementen des Diktats erstellen und Folgendes verwenden
hash()
:Dies ist viel weniger rechenintensiv als das Generieren der JSON-Zeichenfolge oder der Darstellung des Wörterbuchs.
UPDATE: Bitte beachten Sie die folgenden Kommentare, warum dieser Ansatz möglicherweise kein stabiles Ergebnis liefert.
quelle
hash()
Funktion keine stabile Ausgabe erzeugt. Dies bedeutet, dass bei gleicher Eingabe unterschiedliche Ergebnisse mit unterschiedlichen Instanzen desselben Python-Interpreters zurückgegeben werden. Für mich sieht es so aus, als würde bei jedem Start des Interpreters ein Startwert generiert.Die Verwendung
sorted(d.items())
reicht nicht aus, um uns einen stabilen Repräsentanten zu verschaffen. Einige der Werte ind
könnten auch Wörterbücher sein, und ihre Schlüssel werden weiterhin in einer beliebigen Reihenfolge ausgegeben. Solange alle Schlüssel Zeichenfolgen sind, bevorzuge ich:Das heißt, wenn die Hashes auf verschiedenen Computern oder Python-Versionen stabil sein müssen, bin ich mir nicht sicher, ob dies kugelsicher ist. Möglicherweise möchten Sie die Argumente
separators
und hinzufügenensure_ascii
, um sich vor Änderungen an den dortigen Standardeinstellungen zu schützen. Ich würde mich über Kommentare freuen.quelle
ensure_ascii
Argument würde vor diesem völlig hypothetischen Problem schützen.make_hash
. gist.github.com/charlax/b8731de51d2ea86c6eb9default=str
demdumps
Befehl hinzufügen . Scheint gut zu funktionieren.BEARBEITEN : Wenn alle Ihre Schlüssel Zeichenfolgen sind , lesen Sie bitte Jack O'Connors wesentlich einfachere (und schnellere) Lösung (die auch für das Hashing verschachtelter Wörterbücher funktioniert ) , bevor Sie diese Antwort weiter lesen .
Obwohl eine Antwort akzeptiert wurde, lautet der Titel der Frage "Hashing a Python Dictionary", und die Antwort ist in Bezug auf diesen Titel unvollständig. (In Bezug auf den Hauptteil der Frage ist die Antwort vollständig.)
Verschachtelte Wörterbücher
Wenn man im Stapelüberlauf nach dem Hash eines Wörterbuchs sucht, kann man auf diese treffend betitelte Frage stoßen und unbefriedigt bleiben, wenn man versucht, verschachtelte Wörterbücher zu hashen. Die obige Antwort funktioniert in diesem Fall nicht und Sie müssen einen rekursiven Mechanismus implementieren, um den Hash abzurufen.
Hier ist ein solcher Mechanismus:
Bonus: Objekte und Klassen zerschlagen
Die
hash()
Funktion funktioniert hervorragend, wenn Sie Klassen oder Instanzen hashen. Hier ist jedoch ein Problem, das ich mit Hash in Bezug auf Objekte gefunden habe:Der Hash ist der gleiche, auch nachdem ich foo geändert habe. Dies liegt daran, dass sich die Identität von foo nicht geändert hat, sodass der Hash derselbe ist. Wenn Sie möchten, dass foo je nach aktueller Definition unterschiedlich hasht, besteht die Lösung darin, alles zu hashen, was sich tatsächlich ändert. In diesem Fall ist das
__dict__
Attribut:Leider, wenn Sie versuchen, dasselbe mit der Klasse selbst zu tun:
Die class
__dict__
-Eigenschaft ist kein normales Wörterbuch:Hier ist ein ähnlicher Mechanismus wie zuvor, der Klassen angemessen behandelt:
Sie können dies verwenden, um ein Hash-Tupel mit beliebig vielen Elementen zurückzugeben:
HINWEIS: Der gesamte obige Code setzt Python 3.x voraus. In früheren Versionen nicht getestet, obwohl ich davon
make_hash()
ausgehe, dass dies beispielsweise in 2.7.2 funktioniert. Soweit die Beispiele funktionieren, weiß ich dassollte durch ersetzt werden
quelle
hash
Listen und Tupeln hinzugefügt . Andernfalls werden meine Ganzzahllisten verwendet, die zufällig Werte in meinem Wörterbuch sind, und es werden Listen mit Hashes zurückgegeben, was nicht das ist, was ich möchte.Hier ist eine klarere Lösung.
quelle
if isinstance(o,list):
, kannif isinstance(obj, (set, tuple, list)):
diese Funktion für jedes Objekt verwendet werden.Der folgende Code vermeidet die Verwendung der Python-Funktion hash (), da keine Hashes bereitgestellt werden, die bei Neustarts von Python konsistent sind (siehe Hash-Funktion in Python 3.3 gibt unterschiedliche Ergebnisse zwischen Sitzungen zurück ).
make_hashable()
konvertiert das Objekt in verschachtelte Tupel undmake_hash_sha256()
konvertiert denrepr()
in einen base64-codierten SHA256-Hash.quelle
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)))
. Dies ist nicht ganz die Lösung, nach der ich suche, aber es ist eine schöne Zwischenstufe. Ich denke darübertype(o).__name__
nach, den Anfang jedes Tupels zu ergänzen, um die Differenzierung zu erzwingen.tuple(sorted((make_hashable(e) for e in o)))
Aktualisiert von 2013 Antwort ...
Keine der oben genannten Antworten scheint mir zuverlässig zu sein. Der Grund ist die Verwendung von items (). Soweit ich weiß, erfolgt dies in maschinenabhängiger Reihenfolge.
Wie wäre es stattdessen damit?
quelle
dict.items
keine vorhersehbar geordnete Liste zurückgegeben wird?frozenset
kümmert sich darumhash
darüber im Klaren sein , dass es der integrierten Funktion egal ist, wie der Frozenset-Inhalt gedruckt wird oder so etwas. Testen Sie es in mehreren Maschinen und Python-Versionen und Sie werden sehen.Um die Schlüsselreihenfolge beizubehalten, würde ich anstelle von
hash(str(dictionary))
oderhash(json.dumps(dictionary))
eine schnelle und schmutzige Lösung bevorzugen:Es funktioniert auch für Typen wie
DateTime
und mehr, die nicht JSON-serialisierbar sind.quelle
Sie können das
frozendict
Modul eines Drittanbieters verwenden , um Ihr Diktat einzufrieren und es hashbar zu machen.Für den Umgang mit verschachtelten Objekten können Sie Folgendes verwenden:
Wenn Sie weitere Typen unterstützen möchten, verwenden Sie
functools.singledispatch
(Python 3.7):quelle
dict
vonDataFrame
Objekten.elif
Klausel hinzugefügt , damit es mitDataFrame
s funktioniert :elif isinstance(x, pd.DataFrame): return make_hashable(hash_pandas_object(x).tolist())
Ich werde die Antwort bearbeiten und sehen, ob Sie sie akzeptieren ...hash
Randomisierung ist eine absichtliche Sicherheitsfunktion, die in Python 3.7 standardmäßig aktiviert ist.Sie können dazu die Kartenbibliothek verwenden. Insbesondere maps.FrozenMap
Zum Installieren
maps
einfach:Es behandelt auch den verschachtelten
dict
Fall:Haftungsausschluss: Ich bin der Autor der
maps
Bibliothek.quelle
.recurse
. Siehe maps.readthedocs.io/en/latest/api.html#maps.FrozenMap.recurse . Das Bestellen in Listen ist semantisch sinnvoll. Wenn Sie die Unabhängigkeit der Reihenfolge wünschen, können Sie Ihre Listen vor dem Aufruf in Sets konvertieren.recurse
. Sie können denlist_fn
Parameter auch verwenden.recurse
, um eine andere hashable Datenstruktur alstuple
(.egfrozenset
) zu verwendenEine Möglichkeit, sich dem Problem zu nähern, besteht darin, ein Tupel aus den Elementen des Wörterbuchs zu erstellen:
quelle
So mach ich es:
quelle
hash(str({'a': 1, 'b': 2})) != hash(str({'b': 2, 'a': 1}))
(obwohl es für einige Wörterbücher funktioniert, kann nicht garantiert werden, dass es für alle funktioniert).