La serializzazione e deserializzazione degli oggetti Python è un aspetto importante di qualsiasi programma non banale. Se in Python si salva qualcosa in un file, se si legge un file di configurazione o se si risponde a una richiesta HTTP, si esegue la serializzazione degli oggetti e la deserializzazione.
In un certo senso, la serializzazione e la deserializzazione sono le cose più noiose al mondo. A chi importa di tutti i formati e protocolli? Devi solo mantenere o trasmettere alcuni oggetti Python e recuperarli in un secondo momento.
Questo è un modo molto salutare per guardare il mondo a livello concettuale. Ma, a livello pragmatico, quale schema di serializzazione, formato o protocollo si sceglie può determinare quanto velocemente il programma viene eseguito, quanto è sicuro, quanta libertà è necessario mantenere il proprio stato e quanto bene si intende interoperare con altri sistemi.
La ragione per cui ci sono così tante opzioni è che le diverse circostanze richiedono soluzioni diverse. Non esiste "taglia unica". In questo tutorial in due parti esaminerò i pro e i contro degli schemi di serializzazione e deserializzazione di maggior successo, mostrerò come usarli e forniremo delle linee guida per scegliere tra di loro di fronte a un caso d'uso specifico.
Nelle sezioni seguenti serializzerò e deserializzerò gli stessi grafici di oggetti Python usando diversi serializzatori. Per evitare la ripetizione, definirò qui questi grafici degli oggetti.
Il semplice oggetto graph è un dizionario che contiene un elenco di interi, una stringa, un float, un booleano e un None.
simple = dict (int_list = [1, 2, 3], text = "stringa", numero = 3.44, booleano = True, none = Nessuno)
Il complesso oggetto grafico è anche un dizionario, ma contiene a appuntamento
oggetto e istanza della classe definita dall'utente che ha a self.simple
attributo, che è impostato sul semplice oggetto grafico.
da datetime import classe datetime A (oggetto): def __init __ (self, simple): self.simple = simple def __eq __ (self, other): if hasattrattr (altro, 'simple'): return False return self.simple == other.simple def __ne __ (self, other): se non hasattr (other, 'simple'): return True return self.simple! = other.simple complex = dict (a = A (semplice), when = datetime (2016, 3, 7))
Il sottaceto è un alimento base. È un formato di serializzazione di oggetti Python nativo. L'interfaccia pickle fornisce quattro metodi: dump, dump, load e load. Il dump ()
il metodo serializza su un file aperto (oggetto simile a un file). Il discariche ()
il metodo si serializza su una stringa. Il caricare()
metodo deserializza da un oggetto simile a un file aperto. Il carichi ()
metodo deserializza da una stringa.
Pickle supporta di default un protocollo testuale, ma ha anche un protocollo binario, che è più efficiente, ma non leggibile dall'uomo (utile durante il debug).
Ecco come si mette sottosopra un oggetto grafico Python in una stringa e in un file usando entrambi i protocolli.
import cPickle come pickle pickle.dumps (simple) "(dp1 \ nS'text '\ np2 \ nS'string' \ np3 \ nsS'none '\ np4 \ nNsS'boolean' \ np5 \ nI01 \ nsS'number '\ np6 \ nF3.4399999999999999 \ nsSeleziona '\ np7 \ n (lp8 \ nI1 \ naI2 \ naI3 \ nas. "pickle.dumps (semplice, protocollo = pickle.HIGHEST_PROTOCOL)' \ x80 \ x02 q \ x01 (U \ x04textq \ x02U \ x06stringq \ x03U \ x04noneq \ x04NU \ x07boolean \ x88U \ x06numberq \ x05G @ \ x0b \ x85 \ x1e \ xb8Q \ XEB \ x85U \ x08int_list] q \ x06 (K \ x01K \ x02K \ x03eu.'
La rappresentazione binaria può sembrare più grande, ma questa è un'illusione dovuta alla sua presentazione. Quando si esegue il dumping su un file, il protocollo testuale è 130 byte, mentre il protocollo binario è solo 85 byte.
pickle.dump (semplice, aperto ('simple1.pkl', 'w')) pickle.dump (semplice, aperto ('simple2.pkl', 'wb'), protocollo = pickle.HIGHEST_PROTOCOL) ls -la sim *. * -rw-r - r-- 1 gigi staff 130 mar 9 02:42 simple1.pkl -rw-r - r-- 1 gigi staff 85 mar 9 02:43 simple2.pkl
Disimpegnare da una stringa è semplice come:
x = pickle.loads ("(dp1 \ nS'testo '\ np2 \ nS'string' \ np3 \ nsS'none '\ np4 \ nNsS'booleano' \ np5 \ nI01 \ nsSnumero '\ np6 \ nF3.4399999999999999 \ nsSeleziona '\ np7 \ n (lp8 \ nI1 \ naI2 \ naI3 \ nas. ") asserisci x == semplice x = pickle.loads (' \ x80 \ x02 q \ x01 (U \ x04textq \ x02U \ x06stringq \ x03U \ x04noneq \ x04NU \ x07boolean \ x88U \ x06numberq \ x05G @ \ x0b \ x85 \ x1e \ xb8Q \ xeb \ x85U \ x08int_list] q \ x06 (K \ x01K \ x02K \ x03eu. ') asserisci x == semplice
Nota che pickle può capire automaticamente il protocollo. Non è necessario specificare un protocollo anche per quello binario.
Disimpegnare da un file è altrettanto facile. Hai solo bisogno di fornire un file aperto.
x = pickle.load (open ('simple1.pkl')) asser x == simple x = pickle.load (open ('simple2.pkl')) asser x == simple x = pickle.load (open ('simple2 .pkl ',' rb ')) asserisce x == semplice
Secondo la documentazione, dovresti aprire sottaceti binari usando la modalità 'rb', ma come puoi vedere funziona in entrambi i modi.
Vediamo come il sottaceto si occupa del complesso grafico degli oggetti.
pickle.dumps (complex) "(dp1 \ nS'a '\ nccopy_reg \ n_reconstructor \ np2 \ n (c__main __ \ nA \ np3 \ nc__builtin __ \ nobject \ np4 \ nNtRp5 \ n (dp6 \ nS'semplice' \ np7 \ n ( DP8 \ nS'text '\ NP9 \ nS'string' \ NP10 \ nsS'none '\ NP11 \ nNsS'boolean' \ NP12 \ nI01 \ nsS'number '\ NP13 \ nF3.4399999999999999 \ nsS'int_list' \ np14 \ n (LP15 \ NI1 \ naI2 \ naI3 \ nassbsS'when '\ NP16 \ ncdatetime \ ndatetime \ NP17 \ n (S' \\ x07 \\ xe0 \\ x03 x07 \\ \\ x00 x00 \\ \\ \\ X00 x00 \\ x00 \\ x00 '\ ntRp18 \ ns. "pickle.dumps (complesso, protocollo = pickle.HIGHEST_PROTOCOL)' \ x80 \ x02 q \ x01 (U \ x01ac__main __ \ nA \ nq \ x02) \ x81q \ x03 q \ x04U \ x06simpleq \ x05 q \ x06 (U \ x04textq \ x07U \ x06stringq \ x08U \ x04noneq \ TNU \ x07boolean \ x88U \ x06numberq \ nG @ \ x0b \ x85 \ x1e \ xb8Q \ XEB \ x85U \ x08int_list] q \ x0b (K \ x01K \ x02K \ x03eusbU \ x04whenq \ x0ccdatetime \ ndatetime \ nq \ rU \ n \ x07 \ xe0 \ x03 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x85Rq \ x0eu. 'sottaceto .dump (complesso, aperto ('complex1.pkl', 'w')) pickle.dump (complesso, aperto ('complex2.pkl', 'wb'), protocollo = pickle.HIGHEST_PROTOCOL) ls -la comp *. * -rw-r - r-- 1 gigi staff 327 mar 9 02:58 complex1.pkl -rw-r - r-- 1 gigi staff 171 9 marzo 02:58 complex2.pkl
L'efficienza del protocollo binario è ancora maggiore con i grafici di oggetti complessi.
JSON (JavaScript Object Notation) fa parte della libreria standard Python da Python 2.5. Lo considererò un formato nativo a questo punto. È un formato basato su testo ed è il re non ufficiale del web per quanto riguarda la serializzazione degli oggetti. Il suo sistema tipo ovviamente modella JavaScript, quindi è piuttosto limitato.
Facciamo serializzare e deserializzare i grafici degli oggetti semplici e complessi e vediamo cosa succede. L'interfaccia è quasi identica all'interfaccia pickle. Hai dump ()
, discariche ()
, caricare()
, e carichi ()
funzioni. Ma non ci sono protocolli da selezionare, e ci sono molti argomenti opzionali per controllare il processo. Iniziamo semplicemente scaricando il semplice oggetto grafico senza argomenti speciali:
import json print json.dumps (simple) "text": "string", "none": null, "boolean": true, "number": 3.44, "int_list": [1, 2, 3]
L'output sembra abbastanza leggibile, ma non c'è indentazione. Per un grafico a oggetti più grande, questo può essere un problema. Facciamo rientrare l'output:
print json.dumps (simple, indent = 4) "text": "string", "none": null, "boolean": true, "number": 3.44, "int_list": [1, 2, 3]
Sembra molto meglio. Passiamo al grafico degli oggetti complessi.
json.dumps (complex) -------------------------------------------- ------------------------------- TypeError Traceback (ultima chiamata più recente)nel () ----> 1 json.dumps (complex) /usr/local/Cellar/python/2.7.10/Framework/Python.framework/Versions/2.7/lib/python2.7/json/__init__.pyc nelle discariche (obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separatori, encoding, default, sort_keys, ** kw) 241 cls è None e il rientro è None ei separatori sono None e 242 encoding == 'utf-8' e default è None e non sort_keys e non kw): -> 243 return _default_encoder.encode (obj) 244 se cls è None: 245 cls = JSONEncoder /usr/local/Cellar/python/2.7.10/Framework/Python.framework /Versions/2.7/lib/python2.7/json/encoder.pyc in encode (self, o) 205 # eccezioni non sono così dettagliate. La lista chiamata dovrebbe essere approssimativamente 206 # equivalente a PySequence_Fast che ".join () dovrebbe fare. -> 207 chunks = self.iterencode (o, _one_shot = True) 208 se non è istanza (blocchi, (lista, tupla)) : 209 chunks = list (chunks) /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.pyc in iterencode (self, o, _one_shot ) 268 self.key_separator, self.item_separator, self.sort_keys, 269 self.skipkeys, _one_shot) -> 270 return _iterencode (o, 0) 271 272 def _make_iterencode (marker, _default, _encoder, _indent, _floatstr, / usr / local / Cellar / python / 2.7.10 / Frameworks / Python.framework / Versions / 2.7 / lib / python2.7 / json / encoder.pyc di default (self, o) 182 183 "" "-> 184 raise TypeError ( repr (o) + "non è serializzabile in JSON") 185 186 def encode (self, o): TypeError: <__main__.A object at 0x10f367cd0> non è serializzabile con JSON
Whoa! Non sembra affatto bello. Quello che è successo? Il messaggio di errore è che l'oggetto A non è serializzabile in JSON. Ricorda che JSON ha un sistema di tipi molto limitato e non può serializzare automaticamente le classi definite dall'utente. Il modo per affrontarlo è la sottoclasse della classe JSONEncoder usata dal modulo json e implementare il predefinito()
viene chiamato ogni volta che il codificatore JSON viene eseguito in un oggetto che non può essere serializzato.
Il lavoro del codificatore personalizzato è di convertirlo in un grafico di oggetto Python che il codificatore JSON è in grado di codificare. In questo caso abbiamo due oggetti che richiedono una codifica speciale: il appuntamento
oggetto e la classe A. Il seguente encoder fa il lavoro. Ogni oggetto speciale viene convertito in a dict
dove la chiave è il nome del tipo circondato da dunders (doppio underscore). Questo sarà importante per la decodifica.
from datetime import datetime import json class CustomEncoder (json.JSONEncoder): def default (self, o): if isinstance (o, datetime): return '__datetime__': o.replace (microsecond = 0) .isoformat () ritorno Formato '__ __'. (o .__ class __.__ nome__): o .__ dict__
Proviamo ancora con il nostro codificatore personalizzato:
serialized = json.dumps (complex, indent = 4, cls = CustomEncoder) print serialized "a": "__A__": "simple": "text": "stringa", "none": null, "booleano ": true," number ": 3.44," int_list ": [1, 2, 3]," when ": " __datetime__ ":" 2016-03-07T00: 00: 00 "
Questo è bellissimo. Il grafico dell'oggetto complesso è stato serializzato correttamente e le informazioni sul tipo originale dei componenti sono state mantenute tramite le chiavi: "__A__" e "__datetime__". Se usi i dunder per i tuoi nomi, allora devi trovare una convenzione diversa per denotare tipi speciali.
Decodifichiamo il grafico dell'oggetto complesso.
> deserialized = json.loads (serializzato)> deserialized == complex False
Hmmm, la deserializzazione ha funzionato (senza errori), ma è diversa dal grafico oggetto complesso originale che abbiamo serializzato. Qualcosa è sbagliato. Diamo un'occhiata al grafico dell'oggetto deserializzato. Userò il pprint
funzione del pprint
modulo per bella stampa.
> da pprint import pprint> pprint (deserialized) u'a ': u' __ A__ ': u'simple': u'boolean ': True, u'int_list': [1, 2, 3], u 'none': None, u'number ': 3.44, u'text': u'string ', u'when': u '__ datetime__': u'2016-03-07T00: 00: 00 '
Ok. Il problema è che il modulo json non sa nulla della classe A o anche dell'oggetto datetime standard. Deserializza semplicemente tutto per impostazione predefinita sull'oggetto Python che corrisponde al suo sistema di tipi. Per tornare a un ricco oggetto grafico Python, hai bisogno di una decodifica personalizzata.
Non è necessaria una sottoclasse di decodifica personalizzata. Il caricare()
e carichi ()
le funzioni forniscono il parametro "object_hook" che ti consente di fornire una funzione personalizzata che converte i valori in oggetti.
def decode_object (o): if '__A__' in o: a = A () a .__ dict __. update (o ['__ A__']) restituisce un elif '__datetime__' in o: return datetime.strptime (o ['__ datetime__' ], '% Y-% m-% dT% H:% M:% S') restituisce o
Decodifichiamo usando il decode_object ()
funzione come parametro per il carichi ()
parametro object_hook.
> deserialized = json.loads (serializzato, object_hook = decode_object)> print deserialized u'a ': <__main__.A object at 0x10d984790>, u'when ': datetime.datetime (2016, 3, 7, 0, 0)> deserializzato == complesso True
Nella prima parte di questo tutorial, hai imparato a conoscere il concetto generale di serializzazione e deserializzazione degli oggetti Python e hai esplorato gli ins e out della serializzazione degli oggetti Python usando Pickle e JSON.
Nella seconda parte, verrai informato su YAML, sui problemi di prestazioni e sicurezza e una rapida revisione di schemi di serializzazione aggiuntivi.
Impara Python con la nostra guida completa al tutorial su Python, sia che tu stia appena iniziando o che sei un programmatore esperto che cerca di imparare nuove abilità.