Serialisering og deserialisering av Python-objekter Del 1

Python objekt serialisering og deserialisering er et viktig aspekt av et ikke-trivielt program. Hvis du i Python lagrer noe i en fil, hvis du leser en konfigurasjonsfil, eller hvis du svarer på en HTTP-forespørsel, gjør du objekt serialisering og deserialisering. 

På en måte er serialisering og deserialisering de mest kjedelige tingene i verden. Hvem bryr seg om alle formater og protokoller? Du vil bare fortsette eller streame noen Python gjenstander og få dem tilbake senere intakt. 

Dette er en veldig sunn måte å se på verden på konseptnivå. Men på det pragmatiske nivået, hvilken serielliseringsplan, format eller protokoll du velger, kan avgjøre hvor fort programmet kjører, hvor sikkert det er, hvor mye frihet du må opprettholde staten din, og hvor godt du skal samarbeide med andre systemer. 

Grunnen til at det er så mange alternativer er at ulike forhold krever forskjellige løsninger. Det finnes ingen "en størrelse som passer alle". I denne todelte opplæringen vil jeg gå over fordelene og ulemper med de mest vellykkede serialiserings- og deserialiseringsordninger, vise hvordan de skal brukes, og gi retningslinjer for å velge mellom dem når de står overfor en bestemt brukstilfelle.

Kjørereksempel

I de følgende avsnittene serialiseres og deserialiseres de samme Python-objektgrafer ved hjelp av forskjellige serialiseringsverktøy. For å unngå repetisjon definerer jeg disse objektgrafer her.

Enkel Objektgraf

Den enkle objektgrafen er en ordbok som inneholder en liste over heltall, en streng, en float, en boolsk, og en Ingen.

enkel = dikt (int_list = [1, 2, 3], tekst = "streng", tall = 3.44, boolean = True, ingen = Ingen) 

Kompleks Objektgraf

Kompleksobjektgrafen er også en ordbok, men den inneholder en dato tid objekt og brukerdefinert klasseeksempel som har a self.simple attributt, som er satt til den enkle objektgrafen.

fra datetime import datetime klasse A (objekt): def __init __ (selv, enkel): self.simple = enkel def __eq __ (selv, andre): hvis ikke hasattr (andre, 'simple'): returnere falsk retur self.simple == other.simple def __ne __ (selv, andre): hvis ikke hasattr (andre, 'simple'): returnere True return self.simple! = other.simple kompleks = dikt (a = A (enkelt), når = datetime (2016, 3, 7))

pickle

Pickle er en stift. Det er et innfødt Python-objekt serialiseringsformat. Pickle-grensesnittet gir fire metoder: dump, dumper, last og belastninger. De dump () Metoden serialiserer til en åpen fil (fillignende objekt). De dumper() Metoden serialiserer til en streng. De laste() Metoden deserialiserer fra en åpen fillignende gjenstand. De laster() Metoden deserialiserer fra en streng.

Pickle støtter som standard en tekstprotokoll, men har også en binær protokoll, som er mer effektiv, men ikke menneskelig lesbar (nyttig når feilsøking).

Slik henter du en Python-objektgraf til en streng og en fil ved hjelp av begge protokollene.

importer cPickle som pickle pickle.dumps (enkel) "(dp1 \ nS'text '\ np2 \ nS'string' \ np3 \ nSSone \ np4 \ nSsboolean '\ np5 \ nI01 \ nsS'number' \ np6 \ nF3.4399999999999999 \ nsS'int_list '\ np7 \ n (lp8 \ nI1 \ naI2 \ naI3 \ nas. "pickle.dumps (enkel, protokoll = 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.'

Den binære representasjonen kan virke større, men dette er en illusjon på grunn av presentasjonen. Ved dumping til en fil er tekstprotokollen 130 bytes, mens den binære protokollen bare er 85 byte.

pickle.dump (enkel, åpen ('simple1.pkl', 'w')) pickle.dump (enkel, åpen ('simple2.pkl', 'wb'), protokoll = pickle.HIGHEST_PROTOCOL) ls -la sim *. * -rw-r - r-- 1 gigi-staben 130 Mar 9 02:42 simple1.pkl -rw-r - r-- 1 gigi-staben 85 Mar 9 02:43 simple2.pkl

Unpickling fra en streng er like enkelt som:

x = pickle.loads ("(dp1 \ nS'text '\ np2 \ nS'string' \ np3 \ nSSone '\ np4 \ nSsboolean' \ np5 \ nI01 \ nsS'number '\ np6 \ nF3.4399999999999999 \ nsS'int_list '\ np7 \ n (lp8 \ nI1 \ naI2 \ naI3 \ nas. ") assert x == enkel x = pickle.loads (' \ x80 \ x02 q \ x01 (U \ x04textq \ x02U \ x06stringq \ x03U \ x04noneq \ x04NU \ x07boolean \ x88U \ x06numberq \ x05G @ \ x0b \ x85 \ x08int_list] q \ x06 (K \ x01K \ x02K \ x03eu. ') hevde x == enkel

Merk at pickle kan finne ut protokollen automatisk. Det er ikke nødvendig å spesifisere en protokoll selv for den binære.

Unpickling fra en fil er like enkelt. Du trenger bare å gi en åpen fil.

x = pickle.load (åpen ('simple1.pkl')) assert x == enkel x = pickle.load (åpen ('simple2.pkl')) assert x == enkel x = pickle.load .pkl ',' rb ')) hev x == enkelt

Ifølge dokumentasjonen skal du åpne binære pickles ved hjelp av rb-modusen, men som du ser, virker det på en eller annen måte.

La oss se hvordan pickle omhandler komplekse objektgrafen.

pickle.dumps (komplekse) "(dp1 \ nS'a '\ nccopy_reg \ n_reconstructor \ np2 \ n (c__main __ \ nA \ np3 \ nc__builtin __ \ nobject \ np4 \ nNtRp5 \ n (dp6 \ nS'simple' \ 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' \\ \\ x 07 xe0 \\ \\ x 07 X03 x00 \\ \\ \\ x00 x00 \\ x00 \\ x00 \\ x00 '\ ntRp18 \ ns. "pickle.dumps (komplekst, protokoll = 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 \ r \ x07 \ x00 \ x00 \ x00 \ x00 \ x00 \ x85Rq \ x0eu. 'pickle .dump (kompleks, åpen ('complex1.pkl', 'w')) pickle.dump (kompleks, åpen ('complex2.pkl', 'wb'), protokoll = pickle.HIGHEST_PROTOCOL) ls -la komp *. * -rw-r - r-- 1 gigi-stab 327 mar 9 02:58 complex1.pkl -rw-r - r-- 1 gigi-staben 171 Mar 9 02:58 complex2.pkl

Effektiviteten til den binære protokollen er enda større med komplekse objektgrafer.

JSON

JSON (JavaScript Object Notation) har vært en del av Python standardbiblioteket siden Python 2.5. Jeg ser det som et innfødt format på dette tidspunktet. Det er et tekstbasert format og er uoffisiell konge av nettet så langt som objekt serialisering går. Dens type system naturlig modeller JavaScript, så det er ganske begrenset. 

La oss serialisere og deserialisere de enkle og komplekse objekternes grafer og se hva som skjer. Grensesnittet er nesten identisk med picklegrensesnittet. Du har dump (), dumper(), laste(), og laster() funksjoner. Men det er ingen protokoller å velge, og det er mange valgfrie argumenter for å kontrollere prosessen. La oss starte enkle ved å dumpe den enkle objektgrafen uten noen spesielle argumenter:

importer json print json.dumps (enkelt) "text": "streng", "ingen": null, "boolean": true, "number": 3,44, "int_list": [1, 2, 3]

Utgangen ser ganske lesbar ut, men det er ingen innrykk. For en større objektgraf kan dette være et problem. La oss hente utgangen:

skriv ut json.dumps (enkelt, inntastet = 4) "tekst": "streng", "ingen": null, "booleansk": sant, "tall": 3,44, "int_list": [1, 2, 3]

Det ser mye bedre ut. La oss gå videre til kompleksobjektgrafen.

json.dumps (kompleks) -------------------------------------------- ------------------------------- TypeError Traceback (siste samtalen sist)  i () ----> 1 json.dumps (komplekse) /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.pyc i dumper (obj, skipkeys, secure_ascii, check_circular, allow_nan, cls, innrykk, separatorer, koding, standard, sort_keys, ** kw) 241 cls er Ingen og innrykk er Ingen og separatorer er Ingen og 242 koding == 'utf-8' og standard er None og ikke sort_keys og ikke kw): -> 243 return_default_encoder.encode (obj) 244 hvis cls er None: 245 cls = JSONEncoder /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework /Versions/2.7/lib/python2.7/json/encoder.pyc i kode (selv, o) 205 # unntak er ikke like detaljerte. Listekallet skal være omtrent 206 # ekvivalent med PySequence_Fast som ".join () ville gjøre. -> 207 biter = self.iterencode (o, _one_shot = True) 208 hvis ikke isinstance (biter, (liste, tuple)) : 209 biter = liste (biter) /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.pyc i iterencode (selv, o, _one_shot ) 268 self.key_separator, self.item_separator, self.sort_keys, 269 self.skipkeys, _one_shot) -> 270 retur _iterencode (o, 0) 271 272 def _make_iterencode (markører, _default, _encoder, _indent, _floatstr, / usr / Lokal / Kjeller / Python / 2.7.10 / Rammer / Python.framework / Versjoner / 2.7 / lib / python2.7 / json / encoder.pyc som standard (selv, o) 182 183 "" "-> 184 heve TypeError repr (o) + "er ikke JSON serializable") 185 186 def encode (self, o): TypeError: <__main__.A object at 0x10f367cd0> er ikke JSON serializable

Jøss! Det ser ikke bra ut i det hele tatt. Hva skjedde? Feilmeldingen er at A-objektet ikke er JSON-serialiserbart. Husk at JSON har et svært begrenset type system, og det kan ikke serialisere brukerdefinerte klasser automatisk. Måten å adressere det på er å underklasse JSONEncoder-klassen som brukes av json-modulen og implementere misligholde() det kalles når JSON encoderen går inn i en gjenstand det ikke serialiserer. 

Jobben til den egendefinerte encoderen er å konvertere den til en Python-objektgrafikk som JSON-encoderen kan kode inn. I dette tilfellet har vi to objekter som krever spesiell koding: dato tid objekt og A-klassen. Følgende koder gjør jobben. Hvert spesialobjekt konverteres til en dict hvor nøkkelen er navnet på typen omgitt av dunders (dobbel underskrifter). Dette vil være viktig for dekoding. 

fra datetime import datetime import json klasse CustomEncoder (json.JSONEncoder): def default (selv, o): hvis isinstance (o, datetime): return '__datetime__': o.replace (microsecond = 0) .isoformat '__  __' format (o .__ klasse __.__ name__): o .__ dict__

La oss prøve igjen med vår egendefinerte koder:

serialisert = json.dumps (komplekst, indent = 4, cls = CustomEncoder) skriv ut serialisert "a": "__A__": "enkel": "tekst": "streng", "ingen": null, "boolean ": sant" nummer ": 3.44," int_list ": [1, 2, 3]," når ": " __datetime__ ":" 2016-03-07T00: 00: 00 "

Dette er vakkert. Kompleksobjektgrafen ble seriellisert riktig, og den opprinnelige typen informasjonen til komponentene ble beholdt via tastene: "__A__" og "__datetime__". Hvis du bruker dunders for dine navn, må du komme opp med en annen konvensjon for å betegne spesielle typer.

La oss dekode den komplekse objektgrafen.

> deserialisert = json.loads (serialisert)> deserialisert == kompleks falsk

Hmmm, deserialisering arbeidet (ingen feil), men det er annerledes enn den opprinnelige komplekse objektgrafen vi serialiserte. Noe er galt. La oss ta en titt på den deserialiserte objektgrafen. Jeg skal bruke pprint funksjon av pprint modul for fin utskrift.

> fra pprint import pprint> pprint (deserialisert) u'a ': u' __ A__ ': u'imple': u'boolean ': True, u'int_list': [1, 2, 3], du 'ingen': Ingen, du er ': 3.44, u'text': u'string ', u'when': u'__ datetime__ ': u'2016-03-07T00: 00: 00' 

Ok. Problemet er at json-modulen ikke vet noe om A-klassen eller til og med standard datetime-objekt. Det deserialiserer alt som standard til Python-objektet som samsvarer med sitt type system. For å komme tilbake til en rik Python-objektgraf, trenger du tilpasset dekoding. 

Det er ikke behov for en tilpasset dekoder-underklasse. De laste() og laster() funksjoner gir parameteren "object_hook" som lar deg gi en egendefinert funksjon som konverterer dikt til objekter. 

def decode_object (o): hvis '__A__' i o: a = A () a .__ dict __. oppdatering (o ['__ A__']) returnere en elif '__datetime__' i o: return datetime.strptime (o ['__ datetime__' ], '% Y-% m-% dT% H:% M:% S') retur o

La oss dekode ved hjelp av decode_object () fungere som en parameter til laster() object_hook parameter.

> deserialized = json.loads (serialized, object_hook = decode_object)> print deserialized u'a ': <__main__.A object at 0x10d984790>, u'when ': datetime.datetime (2016, 3, 7, 0, 0) deserialisert == kompleks True

Konklusjon

I en del av denne opplæringen har du lært om det generelle konseptet serialisering og deserialisering av Python-objekter og utforsket innsiden og ut av serialiserende Python-objekter ved hjelp av Pickle og JSON. 

I del to lærer du om YAML, ytelses- og sikkerhetsproblemer, og en rask gjennomgang av flere serialiseringsordninger.

Lær python

Lær Python med vår komplette pythonveiledning, enten du er bare i gang eller du er en erfaren coder som ønsker å lære nye ferdigheter..