2016-01-21 5 views
5

Ich habe ein seltsames Verhalten bei der Implementierung von in Python 3 bemerkt, nämlich die Reihenfolge der Schlüssel ändert sich jedes Mal, wenn ich das gleiche Objekt von der Ausführung zur Ausführung ablege. Das Googeln funktionierte nicht, da es mir egal ist, die Schlüssel zu sortieren, ich möchte nur, dass sie gleich bleiben! Hier ist ein Beispiel-Script:Wie behalte ich die JSON-Schlüsselreihenfolge mit Python 3 json.dumps?

import json 

data = { 
    'number': 42, 
    'name': 'John Doe', 
    'email': '[email protected]', 
    'balance': 235.03, 
    'isadmin': False, 
    'groceries': [ 
     'apples', 
     'bananas', 
     'pears', 
    ], 
    'nested': { 
     'complex': True, 
     'value': 2153.23412 
    } 
} 

print(json.dumps(data, indent=2)) 

Wenn ich dieses Skript ausführen bekomme ich verschiedene Ausgänge jedes Mal, zum Beispiel:

$ python print_data.py 
{ 
    "groceries": [ 
    "apples", 
    "bananas", 
    "pears" 
    ], 
    "isadmin": false, 
    "nested": { 
    "value": 2153.23412, 
    "complex": true 
    }, 
    "email": "[email protected]", 
    "number": 42, 
    "name": "John Doe", 
    "balance": 235.03 
} 

Aber dann laufe ich es wieder und ich bekomme:

$ python print_data.py 
{ 
    "email": "[email protected]", 
    "balance": 235.03, 
    "name": "John Doe", 
    "nested": { 
    "value": 2153.23412, 
    "complex": true 
    }, 
    "isadmin": false, 
    "groceries": [ 
    "apples", 
    "bananas", 
    "pears" 
    ], 
    "number": 42 
} 

ich verstehe, dass Wörterbücher ungeordnete Sammlungen sind und dass die Reihenfolge auf einer Hash-Funktion basiert; In Python 2 ist jedoch die Reihenfolge (was auch immer es ist) festgelegt und ändert sich nicht pro Ausführung. Die Schwierigkeit hier ist, dass es meine Tests schwierig zu laufen macht, weil ich die JSON-Ausgabe von zwei verschiedenen Modulen vergleichen muss!

Jede Idee, was los ist? Wie man es repariert? Beachten Sie, dass ich gerne ein OrderedDict oder eine Sortierung vermeiden möchte. Wichtig ist, dass die String-Darstellung zwischen den Ausführungen gleich bleibt. Auch dies ist nur zu Testzwecken und hat keine Auswirkungen auf die Implementierung meines Moduls.

+0

Ich kann garantieren, dass der einzige Grund, dass die Reihenfolge auf Python 2 festgelegt ist, zufällig ist, es sei denn, 'sort_keys = True' –

+0

@WayneWerner ist es nicht zufällig; Hash-Funktionen sind deterministisch - siehe die Kommentare unten, Änderungen der Reihenfolge nach Python 3.3 wegen der Einbeziehung eines zufälligen Hash-Seeds. – bbengfort

+0

Nun, ich stehe korrigiert! Sehr interessant. –

Antwort

8

Python-Wörterbücher und JSON-Objekte sind ungeordnet. Sie könnenjson.dumps() stellen die Schlüssel in der Ausgabe zu sortieren; Dies soll das Testen erleichtern. Verwenden Sie die sort_keys Parameter True:

print(json.dumps(data, indent=2, sort_keys=True)) 

Siehe Why is the order in Python dictionaries and sets arbitrary?, warum Sie eine andere Reihenfolge jedes Mal zu sehen.

Sie können PYTHONHASHSEED environment variable auf einen ganzzahligen Wert setzen, um die Wörterbuchreihenfolge zu "sperren". Verwenden Sie dies nur zum Ausführen von Tests und nicht in der Produktion, da der Hauptzweck der Hash-Randomisierung darin besteht, zu verhindern, dass ein Angreifer Ihr Programm trivialiert.

+0

Von dem Post, den du verlinkt hast, habe ich Folgendes gesucht: "Beachten Sie, dass ab Python 3.3 auch ein zufälliger Hash-Seed verwendet wird, der Hash-Kollisionen unvorhersehbar macht bestimmte Arten von Dienstverweigerungs zu verhindern (wenn ein Angreifer nicht mehr reagiert, indem Masse Hashkollisionen einen Python-Server macht). Dies bedeutet, dass die Reihenfolge eines gegebenen Wörterbuch auf dem Random Hash Samt für den aktuellen Aufruf Python dann auch abhängig ist.“ – bbengfort

+0

Wissen Sie, wie Sie den Hash-Seed für Testzwecke reparieren können? Meine Tests erfordern, dass ich keine zusätzlichen Argumente in die Funktion json.dumps übergebe. – bbengfort

+2

@bbengfort: Sie können die [ 'PYTHONHASHSEED' Umgebungsvariable] eingestellt (https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHASHSEED) auf einen ganzzahligen Wert. –

0

Die Geschichte hinter diesem Verhalten ist this Schwachstelle. Um dies zu verhindern, sollten dieselben Hash-Codes auf einem PC auf einem anderen PC unterschiedlich sein.

Python 2 hat wahrscheinlich dieses Verhalten (Hash Randomizing) standardmäßig wegen Kompatibilität deaktiviert, da dies zum Beispiel doctests brechen würde. Python 3 hat wahrscheinlich (eine Annahme) die Kompatibilität nicht benötigt.

Verwandte Themen