Serialisatie en deserialisatie van Python-objecten deel 1

Python-objectserialisatie en deserialisatie is een belangrijk aspect van elk niet-triviaal programma. Als je in Python iets opslaat in een bestand, als je een configuratiebestand leest, of als je reageert op een HTTP-verzoek, doe je object serialisatie en deserialisatie. 

In zekere zin zijn serialisatie en deserialisatie de saaiste dingen in de wereld. Wie geeft om alle formaten en protocollen? Je wilt gewoon wat Python-objecten behouden of streamen en ze later intact terughalen. 

Dit is een heel gezonde manier om op conceptueel niveau naar de wereld te kijken. Maar, op het pragmatische niveau, welk serialisatieschema, formaat of protocol u kiest, kan bepalen hoe snel uw programma wordt uitgevoerd, hoe veilig het is, hoeveel vrijheid u heeft om uw staat te onderhouden en hoe goed u gaat samenwerken met andere systemen. 

De reden dat er zoveel opties zijn, is dat verschillende omstandigheden om verschillende oplossingen vragen. Er is geen "one size fits all". In deze tweedelige tutorial zal ik de voor- en nadelen van de meest succesvolle serialisatie- en deserialiseringsschema's bespreken, laten zien hoe ze te gebruiken en richtlijnen geven om te kiezen tussen hen wanneer ze worden geconfronteerd met een specifieke use-case.

Lopend voorbeeld

In de volgende secties zal ik dezelfde Python-objectgrafieken met verschillende serializers serialiseren en deserialiseren. Om herhaling te voorkomen, definieer ik deze objectgrafieken hier.

Eenvoudige objectgrafiek

De eenvoudige objectgrafiek is een woordenboek dat een lijst bevat van gehele getallen, een tekenreeks, een float, een boolean en een None.

simple = dict (int_list = [1, 2, 3], text = "string", number = 3.44, boolean = True, none = None) 

Complexe objectgrafiek

De complexe objectgrafiek is ook een woordenboek, maar het bevat een datum Tijd object en door de gebruiker gedefinieerd klasse-instantie met een self.simple attribuut, dat is ingesteld op de eenvoudige objectgrafiek.

van datetime importeren datetime klasse A (object): def __init __ (self, simple): self.simple = simple def __eq __ (zelf, ander): if not hasattr (other, 'simple'): return False return self.simple == other.simple def __ne __ (zelf, anders): if not hasattr (other, 'simple'): return True return self.simple! = other.simple complex = dict (a = A (simple), when = datetime (2016, 3, 7))

Augurk

Augurk is een nietje. Het is een native Python-object serialisatie-indeling. De pickle-interface biedt vier methoden: dump, dumps, load en loads. De dump () methode serialiseert naar een open bestand (bestand-achtig object). De stortplaatsen () methode serialiseert naar een string. De laden() methode deserialiseert van een open, bestandachtig object. De belasting () methode deserialiseert van een string.

Pickle ondersteunt standaard een tekstprotocol, maar heeft ook een binair protocol, dat efficiënter is, maar niet leesbaar voor de mens (handig bij het opsporen van fouten).

Hier ziet u hoe u een Python-objectgrafiek opslaat in een string en in een bestand met behulp van beide protocollen.

importeer cPickle als augurk pickle.dumps (eenvoudig) "(dp1 \ nS'text '\ np2 \ nS'string' \ np3 \ nsS'none '\ np4 \ nNsS'boolean' \ np5 \ nI01 \ nsSnummer '\ np6 \ nF3.4399999999999999 \ nSS'int_list '\ np7 \ n (lp8 \ nI1 \ naI2 \ naI3 \ nas. "pickle.dumps (simple, protocol = 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.'

De binaire weergave lijkt misschien groter, maar dit is een illusie vanwege de presentatie ervan. Wanneer u naar een bestand dumpt, is het tekstprotocol 130 bytes, terwijl het binaire protocol slechts 85 bytes is.

pickle.dump (simple, open ('simple1.pkl', 'w')) pickle.dump (simple, open ('simple2.pkl', 'wb'), protocol = pickle.HIGHEST_PROTOCOL) ls -la sim *. * -rw-r - r-- 1 gigi staff 130 mrt 9 02:42 simple1.pkl -rw-r - r-- 1 gigi staff 85 mrt 9 02:43 simple2.pkl

Loskoppelen van een string is zo simpel als:

x = pickle.loads ("(dp1 \ nS'text '\ np2 \ nS'string' \ np3 \ nsS'none '\ np4 \ nNsS'boolean' \ np5 \ nI01 \ nsSnummer '\ np6 \ nF3.4399999999999999 \ nSS'int_list '\ np7 \ n (lp8 \ nI1 \ naI2 \ naI3 \ nas. ") assert x == simple 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. ') assert x == simple

Merk op dat augurken het protocol automatisch kunnen achterhalen. Het is niet nodig om een ​​protocol op te geven, zelfs voor de binaire.

Loskoppelen van een bestand is net zo eenvoudig. U hoeft alleen maar een open bestand aan te bieden.

x = pickle.load (open ('simple1.pkl')) assert x == simple x = pickle.load (open ('simple2.pkl')) assert x == simple x = pickle.load (open ('simple2 .pkl ',' rb ')) beweren x == eenvoudig

Volgens de documentatie zou je binaire augurken moeten openen met de 'rb'-modus, maar zoals je kunt zien werkt het hoe dan ook.

Laten we eens kijken hoe pickle omgaat met de complexe objectgrafiek.

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 X03 xe0 \\ \\ \\ x07 x00 x00 \\ \\ \\ x00 x00 \\ x00 \\ x00 '\ ntRp18 \ ns. "pickle.dumps (complex, protocol = augurk.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 \ x85Rq \ x0eu. 'augurk .dump (complex, open ('complex1.pkl', 'w')) pickle.dump (complex, open ('complex2.pkl', 'wb'), protocol = pickle.HIGHEST_PROTOCOL) ls -la comp *. * -rw-r - r-- 1 gigi-personeel 327 Mar 9 02:58 complex1.pkl -rw-r - r-- 1 gigi-personeel 171 9 maart 02:58 complex2.pkl

De efficiëntie van het binaire protocol is nog groter met complexe objectgrafieken.

JSON

JSON (JavaScript Object Notation) maakt sinds Python 2.5 deel uit van de Python-standaardbibliotheek. Ik beschouw het op dit moment als een native format. Het is een op tekst gebaseerd formaat en is de niet-officiële koning van het web voor zover serialisatie van objecten gaat. Het type systeem is van nature JavaScript, dus het is behoorlijk beperkt. 

Laten we de eenvoudige en complexe objectgrafieken serialiseren en deserialiseren en zien wat er gebeurt. De interface is bijna identiek aan de interface voor augurken. Jij hebt dump (), stortplaatsen (), laden(), en belasting () functies. Er zijn echter geen protocollen die moeten worden geselecteerd en er zijn veel optionele argumenten om het proces te besturen. Laten we eenvoudig beginnen door de eenvoudige objectgrafiek te dumpen zonder speciale argumenten:

import json print json.dumps (simple) "text": "string", "none": null, "boolean": true, "number": 3.44, "int_list": [1, 2, 3]

De uitvoer ziet er redelijk leesbaar uit, maar er is geen inspringing. Voor een grotere objectgrafiek kan dit een probleem zijn. Laten we de output laten inspringen:

print json.dumps (simple, indent = 4) "text": "string", "none": null, "boolean": true, "number": 3.44, "int_list": [1, 2, 3]

Dat ziet er veel beter uit. Laten we doorgaan naar de complexe objectgrafiek.

json.dumps (complex) -------------------------------------------- ------------------------------- TypeError Traceback (laatste oproep laatste)  in () ----> 1 json.dumps (complex) /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.pyc in dumps (obj, skipkeys, require_ascii, check_circular, allow_nan, cls, inspringing, scheidingstekens, codering, standaard, sorteersleutels, ** kw) 241 cls is None en inspringing is None en de separators is None en 242 encoding == 'utf-8' en standaard is None en niet sort_keys en niet kw): -> 243 return _default_encoder.encode (obj) 244 als cls None is: 245 cls = JSONEncoder /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework /Versions/2.7/lib/python2.7/json/encoder.pyc in encode (zelf, o) 205 # uitzonderingen zijn niet zo gedetailleerd. De lijstoproep zou ongeveer 206 # equivalent moeten zijn aan de PySequence_Fast die ". Join () zou doen. -> 207 chunks = self.iterencode (o, _one_shot = True) 208 zo niet isinstance (chunks, (list, tuple)) : 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 (markers, _default, _encoder, _indent, _floatstr, / usr / local / Cellar / python / 2.7.10 / Frameworks / Python.framework / Versies / 2.7 / lib / python2.7 / json / encoder.pyc in default (self, o) 182 183 "" "-> 184 raise TypeError ( repr (o) + "is niet JSON serialiseerbaar") 185 186 def encode (self, o): TypeError: <__main__.A object at 0x10f367cd0> is niet JSON serialiseerbaar

Whoa! Dat ziet er helemaal niet goed uit. Wat is er gebeurd? De foutmelding is dat het A-object niet JSON-serialiseerbaar is. Onthoud dat JSON een systeem met een zeer beperkt type heeft en dat het niet automatisch door de gebruiker gedefinieerde klassen kan serialiseren. De manier om dit aan te pakken is om de klasse JSONEncoder die door de json-module wordt gebruikt te subclasseren en de standaard() dat wordt aangeroepen wanneer de JSON-encoder tegen een object aanloopt dat niet kan worden geserialiseerd. 

De taak van de aangepaste encoder is om deze te converteren naar een Python-objectgrafiek die de JSON-encoder kan coderen. In dit geval hebben we twee objecten waarvoor speciale codering vereist is: de datum Tijd object en de klasse A. De volgende encoder doet het werk. Elk speciaal object wordt geconverteerd naar een dict waarbij de sleutel de naam is van het type omringd door dunders (dubbele onderstrepingstekens). Dit zal belangrijk zijn voor decodering. 

van datetime importeren datetime importeren json class CustomEncoder (json.JSONEncoder): def default (self, o): if isinstance (o, datetime): return '__datetime__': o.replace (microsecond = 0) .isoformat () retourneer '__  __'. format (o .__ class __.__ name__): o .__ dict__

Laten we het opnieuw proberen met onze aangepaste encoder:

serialized = json.dumps (complex, indent = 4, cls = CustomEncoder) print serialized "a": "__A__": "simple": "text": "string", "none": null, "boolean ": true," number ": 3.44," int_list ": [1, 2, 3]," when ": " __datetime__ ":" 2016-03-07T00: 00: 00 "

Dit is prachtig. De complexe objectgrafiek werd op de juiste manier geserialiseerd en de oorspronkelijke typegegevens van de componenten werden bewaard via de toetsen: "__A__" en "__datetime__". Als u dunders voor uw namen gebruikt, moet u een andere conventie bedenken om speciale typen aan te geven.

Laten we de complexe objectgrafiek decoderen.

> deserialized = json.loads (geserialiseerd)> deserialized == complex False

Hmmm, de deserialisatie werkte (geen fouten), maar het is anders dan de originele complexe objectgrafiek die we hebben geserialiseerd. Er is iets fout. Laten we eens kijken naar de gede-erialiseerde objectgrafiek. Ik gebruik de pprint functie van de pprint module voor mooie afdrukken.

> van pprint import pprint> pprint (gedeserialiseerd) 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. Het probleem is dat de json-module niets weet over de A-klasse of zelfs het standaard datetime-object. Het deserialiseert alles standaard standaard naar het Python-object dat overeenkomt met het type systeem. Om terug te gaan naar een rijke Python-objectgrafiek, hebt u aangepaste decodering nodig. 

Er is geen behoefte aan een aangepaste decodersubklasse. De laden() en belasting () functies bieden de parameter "objecthaak" waarmee u een aangepaste functie kunt opgeven waarmee dictaten naar objecten worden geconverteerd. 

def decode_object (o): if '__A__' in o: a = A () a .__ dict __. update (o ['__ A__']) retourneer een elif '__datetime__' in o: return datetime.strptime (o ['__ datetime__' ], '% Y-% m-% dT% H:% M:% S') terug o

Laten we het decoderen met behulp van de decode_object () functie als een parameter voor de belasting () object_hook-parameter.

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

Conclusie

In deel een van deze tutorial, heb je geleerd over het algemene concept van serialisatie en deserialisatie van Python-objecten en heb je de ins en out van het serialiseren van Python-objecten met Pickle en JSON onderzocht. 

In deel twee leert u meer over YAML, problemen met prestaties en beveiliging en een snel overzicht van aanvullende serialisatieschema's.

Leer Python

Leer Python met onze complete python-handleiding, of je nu net begint of dat je een ervaren coder bent die op zoek is naar nieuwe vaardigheden.