Serializzazione e deserializzazione di oggetti Python parte 2

Questa è la seconda parte di un tutorial sulla serializzazione e deserializzazione degli oggetti Python. Nella prima parte, hai imparato le basi e poi ti sei tuffato nei retroscena di Pickle e JSON. 

In questa parte esplorerai YAML (assicurati di avere l'esempio in esecuzione dalla prima parte), discuti le considerazioni su prestazioni e sicurezza, ottieni una revisione dei formati di serializzazione aggiuntivi e infine impara come scegliere lo schema giusto.

YAML

YAML è il mio formato preferito. Si tratta di un formato di serializzazione dei dati di facile utilizzo. A differenza di Pickle e JSON, non fa parte della libreria standard di Python, quindi è necessario installarlo:

pip installa yaml

Il modulo yaml ha solo caricare() e dump () funzioni. Di default lavorano con stringhe come carichi () e discariche (), ma può prendere un secondo argomento, che è un flusso aperto e quindi può eseguire il dump / load to / from files.

import yaml print yaml.dump (semplice) booleano: vero int_list: [1, 2, 3] nessuno: numero nullo: 3.44 testo: stringa

Nota come YAML leggibile è paragonato a Pickle o persino JSON. E ora per la parte più interessante su YAML: comprende oggetti Python! Nessuna necessità di codificatori e decodificatori personalizzati. Ecco la complessa serializzazione / deserializzazione usando YAML:

> serialized = yaml.dump (complex)> print serialized a: !! python / object: __ main __. Un semplice: booleano: vero int_list: [1, 2, 3] none: numero nullo: 3.44 testo: stringa quando: 2016- 03-07 00:00:00> deserialized = yaml.load (serializzato)> deserializzato == complesso Vero

Come puoi vedere, YAML ha la sua notazione per taggare oggetti Python. L'output è ancora molto leggibile. L'oggetto datetime non richiede alcun tagging speciale perché YAML supporta intrinsecamente oggetti datetime. 

Prestazione

Prima di iniziare a pensare alle prestazioni, devi pensare se le prestazioni sono davvero una preoccupazione. Se serializzi / deserializzi una piccola quantità di dati relativamente di rado (ad esempio leggendo un file di configurazione all'inizio di un programma), le prestazioni non sono davvero una preoccupazione e puoi andare avanti.

Ma, assumendo che tu abbia profilato il tuo sistema e scoperto che la serializzazione e / o la deserializzazione stanno causando problemi di prestazioni, ecco le cose da affrontare.

Ci sono due aspetti per le prestazioni: quanto velocemente puoi serializzare / deserializzare e quanto grande è la rappresentazione serializzata?

Per testare le prestazioni dei vari formati di serializzazione, creerò una struttura di dati più ampia e la serializzerò / deserializzerò usando Pickle, YAML e JSON. Il big_data lista contiene 5.000 oggetti complessi.

big_data = [dict (a = simple, when = datetime.now (). replace (microsecond = 0)) per i in range (5000)]

Salamoia

Userò IPython qui per il suo comodo % timeit funzione magica che misura i tempi di esecuzione.

import cPickle as pickle In [190]:% timeit serialized = pickle.dumps (big_data) 10 loop, meglio di 3: 51 ms per loop In [191]:% timeit deserialized = pickle.loads (serializzato) 10 loop, meglio di 3: 24,2 ms per loop In [192]: deserializzato == big_data Out [192]: True In [193]: len (serializzato) Out [193]: 747328

Il pickle predefinito richiede 83,1 millisecondi per serializzare e 29,2 millisecondi per deserializzare e la dimensione serializzata è 747,328 byte.

Proviamo con il protocollo più alto.

In [195]:% timeit serialized = pickle.dumps (big_data, protocol = pickle.HIGHEST_PROTOCOL) 10 loop, meglio di 3: 21.2 ms per loop In [196]:% timeit deserialized = pickle.loads (serializzato) 10 loop, migliore di 3: 25.2 ms per loop In [197]: len (serializzato) Out [197]: 394350

Risultati interessanti Il tempo di serializzazione si è ridotto a soli 21,2 millisecondi, ma il tempo di deserializzazione è aumentato un po 'a 25,2 millisecondi. Le dimensioni serializzate si riducono significativamente a 394.350 byte (52%).

JSON

In [253]% timeit serializzato = json.dumps (big_data, cls = CustomEncoder) 10 cicli, meglio di 3: 34,7 ms per ciclo In [253]% timeit deserialized = json.loads (serializzato, object_hook = decode_object) 10 loop, migliore di 3: 148 ms per loop In [255]: len (serializzato) Out [255]: 730000

Ok. Le prestazioni sembrano essere un po 'peggio di Pickle per la codifica, ma molto, molto peggio per la decodifica: 6 volte più lento. Cosa sta succedendo? Questo è un artefatto del object_hook funzione che deve essere eseguita per ogni dizionario per verificare se è necessario convertirla in un oggetto. L'esecuzione senza l'hook dell'oggetto è molto più veloce.

% timeit deserialized = json.loads (serializzato) 10 loop, meglio di 3: 36.2 ms per loop

La lezione qui è che quando si serializza e deserializza in JSON, si consideri con molta attenzione qualsiasi codifica personalizzata perché potrebbe avere un impatto significativo sulle prestazioni generali.

YAML

In [293]:% timeit serialized = yaml.dump (big_data) 1 loop, meglio di 3: 1.22 s per loop In [294]:% timeit deserialized = yaml.load (serializzato) 1 loop, meglio di 3: 2.03 s per loop In [295]: len (serializzato) Out [295]: 200091

Ok. YAML è davvero, molto lento. Ma, nota qualcosa di interessante: la dimensione serializzata è di soli 200.091 byte. Molto meglio di entrambi Pickle e JSON. Diamo un'occhiata dentro in modo veloce:

In [300]: stampa serializzata [: 211] - a: & id001 booleano: vero int_list: [1, 2, 3] nessuno: numero nullo: 3.44 testo: stringa quando: 2016-03-13 00:11:44 - a : * id001 quando: 2016-03-13 00:11:44 - a: * id001 quando: 2016-03-13 00:11:44

YAML è molto intelligente qui. Ha identificato che tutti i 5,000 dts condividono lo stesso valore per la chiave 'a', quindi lo memorizza solo una volta e ne fa riferimento usando * id001 per tutti gli oggetti.

Sicurezza

La sicurezza è una preoccupazione spesso critica. Pickle e YAML, in virtù della costruzione di oggetti Python, sono vulnerabili agli attacchi di esecuzione del codice. Un file abilmente formattato può contenere codice arbitrario che verrà eseguito da Pickle o YAML. Non c'è bisogno di allarmarsi. Questo è di progettazione ed è documentato nella documentazione di Pickle:

Avvertenza: il modulo pickle non è destinato a essere protetto da dati errati o costruiti maliziosamente. Non annullare mai i dati ricevuti da una fonte non attendibile o non autenticata.

Così come nella documentazione di YAML:

Avvertenza: non è sicuro chiamare yaml.load con dati ricevuti da una fonte non attendibile! yaml.load è potente come pickle.load e quindi può chiamare qualsiasi funzione Python.

Devi solo capire che non devi caricare i dati serializzati ricevuti da fonti non attendibili usando Pickle o YAML. JSON è OK, ma di nuovo se disponi di codificatori / decodificatori personalizzati di quelli che potresti essere esposto.

Il modulo yaml fornisce il yaml.safe_load () funzione che caricherà solo oggetti semplici, ma in questo modo si perde molta potenza di YAML e si può decidere di utilizzare solo JSON.

Altri formati

Sono disponibili molti altri formati di serializzazione. Eccone alcuni.

Protobuf

Protobuf, o buffer di protocollo, è il formato di scambio di dati di Google. È implementato in C ++ ma ha collegamenti Python. Ha uno schema sofisticato e comprime i dati in modo efficiente. Molto potente, ma non molto facile da usare.

MessagePack

MessagePack è un altro formato di serializzazione popolare. È anche binario ed efficiente, ma a differenza di Protobuf non richiede uno schema. Ha un sistema di tipo simile a JSON, ma un po 'più ricco. Le chiavi possono essere di qualsiasi tipo e non sono supportate solo stringhe e stringhe non UTF8.

CBOR

CBOR è l'acronimo di Concise Binary Object Representation. Di nuovo, supporta il modello di dati JSON. CBOR non è noto come Protobuf o MessagePack ma è interessante per due motivi: 

  1. È uno standard Internet ufficiale: RFC 7049.
  2. È stato progettato specificamente per l'Internet of Things (IoT).

Come scegliere?

Questa è la grande domanda. Con così tante opzioni, come scegli? Consideriamo i vari fattori che dovrebbero essere presi in considerazione:

  1. Il formato serializzato dovrebbe essere leggibile e / o modificabile dall'uomo?
  2. Il contenuto serializzato verrà ricevuto da fonti non attendibili?
  3. La serializzazione / deserializzazione è un collo di bottiglia delle prestazioni?
  4. I dati serializzati devono essere scambiati con ambienti non Python?

Lo renderò molto semplice per te e coprirò diversi scenari comuni e il formato che raccomando per ognuno di essi:

Salvataggio automatico dello stato locale di un programma Python

Usa pickle (cPickle) qui con il HIGHEST_PROTOCOL. È veloce, efficiente e può archiviare e caricare la maggior parte degli oggetti Python senza alcun codice speciale. Può anche essere usato come cache persistente locale.

File di configurazione

Sicuramente YAML. Niente batte la sua semplicità per tutto ciò che gli umani hanno bisogno di leggere o modificare. È usato con successo da Ansible e molti altri progetti. In alcune situazioni, potresti preferire l'uso di moduli Python dritti come file di configurazione. Questa potrebbe essere la scelta giusta, ma non è una serializzazione, e fa parte del programma e non è un file di configurazione separato.

API Web

JSON è il chiaro vincitore qui. Al giorno d'oggi, le API Web vengono utilizzate più spesso da applicazioni Web JavaScript che parlano JSON in modo nativo. Alcune API Web possono restituire altri formati (ad es. Csv per insiemi di risultati tabulari densi), ma direi che è possibile impacchettare i dati csv in JSON con un sovraccarico minimo (non è necessario ripetere ogni riga come un oggetto con tutti i nomi di colonna). 

Comunicazione ad alto volume / bassa latenza su larga scala

Utilizzare uno dei protocolli binari: Protobuf (se è necessario uno schema), MessagePack o CBOR. Esegui i tuoi test per verificare le prestazioni e il potere rappresentativo di ciascuna opzione.

Conclusione

La serializzazione e la deserializzazione degli oggetti Python è un aspetto importante dei sistemi distribuiti. Non è possibile inviare oggetti Python direttamente sul filo. Spesso è necessario interagire con altri sistemi implementati in altri linguaggi e talvolta si desidera semplicemente archiviare lo stato del programma in memoria permanente. 

Python viene fornito con diversi schemi di serializzazione nella sua libreria standard e molti altri sono disponibili come moduli di terze parti. Essere a conoscenza di tutte le opzioni e dei pro e contro di ognuno ti permetterà di scegliere il metodo migliore per la tua situazione.