2016-01-21 8 views
5

Ho notato alcuni strani comportamenti sull'implementazione di Python 3 json.dumps, ovvero l'ordine della chiave cambia ogni volta che eseguo il dump dello stesso oggetto dall'esecuzione all'esecuzione. Googling non funzionava dato che non mi importa di ordinare le chiavi, voglio solo che rimangano le stesse! Ecco uno script di esempio:Come mantenere fisso l'ordine della chiave JSON con 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)) 

Quando eseguo questo script ottengo diverse uscite ogni volta, per esempio:

$ 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 
} 

Ma poi lo eseguo di nuovo e ottengo:

$ 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 
} 

Capisco che i dizionari siano raccolte non ordinate e che l'ordine sia basato su una funzione hash; tuttavia in Python 2 l'ordine (qualunque esso sia) è fisso e non cambia in base all'esecuzione. La difficoltà qui è che rende difficile eseguire i miei test perché ho bisogno di confrontare l'output JSON di due diversi moduli!

Qualche idea su cosa sta succedendo? Come sistemarlo? Nota che mi piacerebbe evitare l'uso di OrderedDict o l'esecuzione di qualsiasi ordinamento e l'importante è che la rappresentazione della stringa rimanga la stessa tra le esecuzioni. Anche questo è solo a scopo di test e non ha alcun effetto sull'implementazione del mio modulo.

+0

Posso garantire che l'unico motivo per cui l'ordine è fisso su Python 2 è accidentale, a meno che 'sort_keys = True' –

+0

@WayneWerner non sia accidentale; le funzioni di hash sono deterministiche - vedi i commenti qui sotto, cambia l'ordine dopo Python 3.3 a causa dell'inclusione di un seme di hash casuale. – bbengfort

+0

Bene, sono corretto! Molto interessante. –

risposta

8

I dizionari Python e gli oggetti JSON sono non ordinati. È possibile possibile chiedere json.dumps() per ordinare le chiavi nell'output; questo ha lo scopo di facilitare i test. Utilizzare il parametro sort_keys-True:

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

Vedi Why is the order in Python dictionaries and sets arbitrary? sul motivo per cui si vede un ordine diverso ogni volta.

È possibile impostare PYTHONHASHSEED environment variable su un valore intero per "bloccare" l'ordine del dizionario; usatelo solo per eseguire test e non in produzione, poiché l'intero punto della randomizzazione dell'hash è quello di impedire a un utente malintenzionato di banchettare con DOS il vostro programma.

+0

Dal post che hai collegato, questo è quello che stavo cercando: "Si noti che a partire da Python 3.3, viene utilizzato anche un seme casuale di hash, rendendo imprevedibili le collisioni hash per prevenire certi tipi di denial of service (laddove un attacker rende un server Python non rispondente causando collisioni hash di massa), ciò significa che l'ordine di un determinato dizionario dipende anche dal seed hash casuale per l'attuale chiamata Python. " – bbengfort

+0

Sapete come risolvere il seme di hash a scopo di test? I miei test richiedono che io non passi argomenti extra nella funzione json.dumps. – bbengfort

+2

@bbengfort: è possibile impostare la variabile di ambiente ['PYTHONHASHSEED'] (https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHASHSEED) su un valore intero. –

0

La storia dietro questo comportamento è la vulnerabilità this. Per impedirlo, gli stessi codici hash su un PC dovrebbero essere diversi su un altro.

Python 2 ha probabilmente disattivato questo comportamento (hash randomizzando) per impostazione predefinita a causa della compatibilità, in quanto ciò potrebbe ad esempio interrompere i doctest. Python 3 probabilmente (un'ipotesi) non ha avuto bisogno della compatibilità.