Python-objekt serialisering och deserialisering är en viktig aspekt av något icke-trivialt program. Om du i Python sparar något till en fil, om du läser en konfigurationsfil eller om du svarar på en HTTP-begäran, gör du objekt serialisering och deserialisering.
I en mening är serialisering och deserialisering de tråkigaste i världen. Vem bryr sig om alla format och protokoll? Du vill bara fortsätta eller strömma några Python objekt och få dem tillbaka senare intakt.
Detta är ett mycket hälsosamt sätt att se världen på konceptuell nivå. Men på den pragmatiska nivån, vilket serialiseringsschema, format eller protokoll du väljer kan avgöra hur snabbt ditt program körs, hur säkert det är, hur mycket frihet du behöver behålla ditt tillstånd och hur väl ska du samverka med andra system.
Anledningen till att det finns så många alternativ är att olika omständigheter kräver olika lösningar. Det finns ingen "en storlek som passar alla". I den här tvådelade handledningen går jag över fördelarna och nackdelarna med de mest framgångsrika serialiserings- och deserialiseringssystemen, visar hur man använder dem och ger riktlinjer för att välja mellan dem när de möter ett specifikt användarfall.
I följande avsnitt serialiserar och deserialiseras samma Python-objektgrafer med olika serialiserare. För att undvika repetition definierar jag dessa objektgrafer här.
Den enkla objektgrafen är en ordlista som innehåller en lista med heltal, en sträng, en float, en boolean och en None.
enkel = dict (int_list = [1, 2, 3], text = "sträng", nummer = 3.44, boolean = sant, ingen = ingen)
Komplexa objektgrafen är också en ordbok, men den innehåller en datum Tid
objekt och användardefinierad klassinstans som har a self.simple
attribut, som är inställt på den enkla objektgrafen.
från datetime import datetime klass A (objekt): def __init __ (själv, enkel): self.simple = enkel def __eq __ (själv, andra): om inte hasattr (annat, "enkelt"): returnera falsk retur self.simple == other.simple def __ne __ (själv, andra): om inte hasattr (annat, "enkelt"): returnera True return self.simple! = other.simple komplex = dict (a = A (enkelt), när = datetime (2016, 3, 7))
Pickle är en häftklammer. Det är ett inbyggt Python-objekt serialiseringsformat. Pickle-gränssnittet innehåller fyra metoder: dumpning, dumper, last och laster. De dumpa()
Metoden serialiserar till en öppen fil (filliknande objekt). De soptippar ()
Metoden serialiserar till en sträng. De ladda()
Metoden deserialiserar från ett öppet filliknande objekt. De massor()
Metoden deserialiserar från en sträng.
Pickle stöder som standard ett textprotokoll, men har också ett binärt protokoll, vilket är mer effektivt men inte mänskligt läsbart (användbart vid felsökning).
Så här hämtar du en Python-objektgraf till en sträng och till en fil med båda protokollen.
importera 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 (enkelt, 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ära representationen kan verka större, men det här är en illusion på grund av dess presentation. Vid dumpning till en fil är textprotokollet 130 byte, medan det binära protokollet endast är 85 byte.
pickle.dump (enkel, öppen ('simple1.pkl', 'w')) pickle.dump (enkel, öppen ('simple2.pkl', 'wb'), protokoll = pickle.HIGHEST_PROTOCOL) ls -la sim *. * -rw-r - r-- 1 gigi-personal 130 mar 9 02:42 simple1.pkl -rw-r - r-- 1 gigi-personal 85 mar 9 02:43 simple2.pkl
Unpickling från en sträng är lika enkelt som:
x = pickle.loads ("(dp1 \ nS'text '\ np2 \ nS'string' \ np3 \ nsS'none '\ np4 \ nNsS'boolean' \ np5 \ nI01 \ nsS'number '\ np6 \ nF3.4399999999999999 \ nsS'int_list '\ np7 \ n (lp8 \ nI1 \ naI2 \ naI3 \ nas. ") hävdar x == enkel x = pickle.loads (' \ x80 \ x02 q \ x01 (U \ x04textq \ x02U \ x06stringq \ x03U \ x04noneq \ x04NU \ x07boolean \ x88U \ x06numberq \ x05G @ \ x0b \ x85 \ x1e \ xb8q \ x85U \ x08int_list] q \ x06 (K \ x01K \ x02K \ x03eu. ') hävdar x == enkel
Observera att pickle kan räkna ut protokollet automatiskt. Det finns inget behov av att ange ett protokoll även för den binära.
Unpickling från en fil är lika enkelt. Du behöver bara ge en öppen fil.
x = pickle.load (open ('simple1.pkl')) assert x == enkel x = pickle.load (öppen ('simple2.pkl')) assert x == enkel x = pickle.load .pkl ',' rb ')) hävdar x == enkelt
Enligt dokumentationen ska du öppna binära pickles med "rb" -läget, men som du kan se fungerar det antingen.
Låt oss se hur pickle handlar om komplexa objektgrafen.
pickle.dumps (complex) "(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' \\ X07 \\ xe0 \\ X03 \\ X07 \\ x00 \\ x00 \\ x00 \\ x00 \\ x00 \\ x00 '\ ntRp18 \ ns. "pickle.dumps (komplext, 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 \ rU \ n \ x07 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x85Rq \ x0eu. .dump (komplex, öppen ('complex1.pkl', 'w')) pickle.dump (komplex, öppen ('complex2.pkl', 'wb'), protokoll = pickle.HIGHEST_PROTOCOL) ls -la comp *. * -rw-r - r-- 1 gigi-personal 327 mar 9 02:58 complex1.pkl -rw-r - r-- 1 gigi-personal 171 Mar 9 02:58 complex2.pkl
Effektiviteten hos det binära protokollet är ännu större med komplexa objektgrafer.
JSON (JavaScript Object Notation) har varit en del av Python standardbiblioteket sedan Python 2.5. Jag anser att det är ett inbyggt format vid denna tidpunkt. Det är ett textbaserat format och är den inofficiella kungen på webben när det gäller objekt serialisering. Dess typsystem använder naturligtvis JavaScript, så det är ganska begränsat.
Låt oss serialisera och deserialisera de enkla och komplexa objektgraferna och se vad som händer. Gränssnittet är nästan identiskt med picklegränssnittet. Du har dumpa()
, soptippar ()
, ladda()
, och massor()
funktioner. Men det finns inga protokoll att välja, och det finns många valfria argument för att styra processen. Låt oss börja enkelt genom att dumpa den enkla objektgrafen utan några speciella argument:
importera json print json.dumps (simple) "text": "sträng", "none": null, "boolean": true, "number": 3.44, "int_list": [1, 2, 3]
Utsignalen ser ganska läsbar ut, men det finns ingen inryckning. För ett större objektgraf kan detta vara ett problem. Låt oss ange utdata:
skriva ut json.dumps (enkel, indent = 4) "text": "sträng", "none": null, "boolean": true, "number": 3.44, "int_list": [1, 2, 3]
Det ser mycket bättre ut. Låt oss gå vidare till komplexa objektgrafen.
json.dumps (komplex) -------------------------------------------- ------------------------------- TypeError Traceback (senaste samtal sist)i () ----> 1 json.dumps (complex) /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.pyc i dumpar (obj, skipkeys, secure_ascii, check_circular, allow_nan, cls, indent, separatorer, kodning, standard, sort_keys, ** kw) 241 cls är Inga och indrag är None och separatorer är None och 242 encoding == 'utf-8' och standard är None och inte sort_keys och inte kw): -> 243 returnera _default_encoder.encode (obj) 244 om cls är None: 245 cls = JSONEncoder /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework /Versions/2.7/lib/python2.7/json/encoder.pyc i encode (self, o) 205 # undantag är inte så detaljerade. Listanropet bör vara ungefär 206 # ekvivalent med PySequence_Fast som ".join () skulle göra." -> 207 chunks = self.iterencode (o, _one_shot = True) 208 om inte isinstance (bitar, (lista, tuple)) : 209 bitar = lista (bitar) /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.pyc i iterencode (själv, 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 / Local / Cellar / python / 2.7.10 / Framkallar / Python.framework / Versions / 2.7 / lib / python2.7 / json / encoder.pyc som standard (själv, o) 182 183 "" "-> 184 höja TypeError repr (o) + "är inte JSON-serialiserbar") 185 186 def encode (self, o): TypeError: <__main__.A object at 0x10f367cd0> är inte JSON serializable
Oj! Det ser inte bra ut alls. Vad hände? Felmeddelandet är att A-objektet inte är JSON-serialiserbart. Kom ihåg att JSON har ett mycket begränsat typsystem och det kan inte automatiskt serialisera användardefinierade klasser. Sättet att ta itu med är att subklassera JSONEncoder-klassen som används av json-modulen och implementera standard()
som kallas när JSON-kodaren går in i ett objekt som det inte kan serialisera.
Arbetet med den anpassade kodaren är att konvertera den till en Python-objektgraf som JSON-kodaren kan koda. I det här fallet har vi två objekt som kräver speciell kodning: datum Tid
objekt och A-klassen. Följande kodare gör jobbet. Varje specialobjekt omvandlas till en dict
där nyckeln är namnet på den typ som omges av dundrar (dubbla underskrifter). Detta kommer att vara viktigt för avkodning.
från datetime import datetime import json klass CustomEncoder (json.JSONEncoder): def default (själv, o): om isinstance (o, datetime): returnera '__datetime__': o.replace (microsecond = 0) .isoformat '__ __' format (o .__ klass __.__ name__): o .__ dict__
Låt oss försöka igen med vår anpassade kodare:
serialized = json.dumps (complex, indent = 4, cls = CustomEncoder) skriv ut serialiserad "a": "__A__": "simple": "text": "sträng", "none": null, "boolean ": sant" nummer ": 3.44," int_list ": [1, 2, 3]," när ": " __datetime__ ":" 2016-03-07T00: 00: 00 "
Detta är vackert. Komplexa objektgrafen serielliserades ordentligt, och komponenternas ursprungliga typinformation behölls via tangenterna: "__A__" och "__datetime__". Om du använder dundrar för dina namn måste du komma med en annan konvention för att beteckna speciella typer.
Låt oss avkoda komplexa objektgrafen.
> deserialized = json.loads (serialized)> deserialized == complex False
Hmmm, deserialiseringen fungerade (inga fel), men det är annorlunda än det ursprungliga komplexa objektgrafen vi serialiserade. Något är fel. Låt oss ta en titt på den deserialiserade objektgrafen. Jag ska använda pprint
funktion av pprint
modul för fin utskrift.
> från pprint import pprint> pprint (deserialiserad) u'a ': u' __ A__ ': u'imple': u'boolean ': True, u'int_list': [1, 2, 3], du 'ingen': Ingen, du talar ': 3.44, u'text': u'string ', då är du: du'__ datetime__: u'2016-03-07T00: 00: 00'
Ok. Problemet är att json-modulen inte vet någonting om A-klassen eller till och med det normala datetime-objektet. Det deserialiserar allt som standard till Python-objektet som matchar sitt typsystem. För att komma tillbaka till en rik Python-objektgraf behöver du anpassad avkodning.
Det finns inget behov av en anpassad dekoder-underklass. De ladda()
och massor()
funktionerna ger parameteren "object_hook" som låter dig skapa en anpassad funktion som konverterar dikt till objekt.
def decode_object (o): om '__A__' i o: a = A () a .__ dict __. uppdatering (o ['__ A__']) returnera en elif '__datetime__' i o: returnera datetime.strptime (o ['__ datetime__' ], '% Y-% m-% dT% H:% M:% S') returnera o
Låt oss avkoda med decode_object ()
fungera som en parameter till massor()
object_hook parameter.
> deserialized = json.loads (serialized, object_hook = decode_object)> skriv deserialized u'a ': <__main__.A object at 0x10d984790>, u'when ': datetime.datetime (2016, 3, 7, 0, 0)> deserialized == complex True
I en del av denna handledning har du lärt dig om det allmänna begreppet serialisering och deserialisering av Python-objekt och utforskade ins och ut ur serialiserande Python-objekt med Pickle och JSON.
I del två lär du dig information om YAML, prestanda och säkerhetsproblem och en snabb genomgång av ytterligare serialiseringssystem.
Lär dig Python med vår kompletta handledning för pythonhandledning, oavsett om du bara har börjat eller du är en erfaren kodare som vill lära dig nya färdigheter.