Die Serialisierung und Deserialisierung von Python-Objekten ist ein wichtiger Aspekt jedes nicht-trivialen Programms. Wenn Sie in Python etwas in einer Datei speichern, eine Konfigurationsdatei lesen oder auf eine HTTP-Anforderung antworten, führen Sie die Objektserialisierung und -deserialisierung durch.
In gewissem Sinne sind Serialisierung und Deserialisierung die langweiligsten Dinge der Welt. Wer kümmert sich um alle Formate und Protokolle? Sie möchten nur einige Python-Objekte beibehalten oder streamen und sie später wieder erhalten.
Dies ist eine sehr gesunde Art, die Welt auf konzeptioneller Ebene zu betrachten. Auf der pragmatischen Ebene kann jedoch anhand des von Ihnen gewählten Serialisierungsschemas, -formats oder -protokolls bestimmt werden, wie schnell Ihr Programm abläuft, wie sicher es ist, wie viel Freiheit Sie haben, um Ihren Status aufrechtzuerhalten und wie gut Sie mit anderen zusammenarbeiten können Systeme.
Der Grund, warum es so viele Möglichkeiten gibt, ist, dass unterschiedliche Umstände unterschiedliche Lösungen erfordern. Es gibt keine Einheitsgröße. In diesem zweiteiligen Tutorial werde ich die Vor-und Nachteile der erfolgreichsten Serialisierungs- und Deserialisierungsschemata durchgehen, zeigen, wie sie verwendet werden, und Richtlinien für die Auswahl zwischen ihnen vor einem bestimmten Anwendungsfall geben.
In den folgenden Abschnitten werden dieselben Python-Objektdiagramme mit verschiedenen Serialisierern serialisiert und deserialisiert. Um Wiederholungen zu vermeiden, definiere ich diese Objektdiagramme hier.
Das einfache Objektdiagramm ist ein Wörterbuch, das eine Liste von Ganzzahlen, eine Zeichenfolge, einen Float, einen Boolean-Wert und einen Keine enthält.
simple = dict (int_list = [1, 2, 3], text = "string", number = 3.44, boolean = True, none = None)
Der komplexe Objektgraph ist auch ein Wörterbuch, enthält jedoch ein Terminzeit
Objekt- und benutzerdefinierte Klasseninstanz, die über eine selbsteinfach
Attribut, das auf das einfache Objektdiagramm gesetzt ist.
von datetime import datetime Klasse A (Objekt): def __init __ (selbst, einfach): self.simple = einfach def __eq __ (selbst, anderes): wenn nicht hasattr (other, 'simple'): return False return self.simple == other.simple def __ne __ (self, other): wenn nicht hasattr (other, 'simple'): return True return self.simple! = other.simple complex = dict (a = A (einfach), wenn = datetime (2016, 3, 7))
Pickle ist eine Klammer. Es ist ein systemeigenes Python-Objektserialisierungsformat. Die Pickle-Schnittstelle bietet vier Methoden: Dump, Dump, Load und Load. Das dump ()
Methode serialisiert zu einer geöffneten Datei (dateiähnliches Objekt). Das Dumps ()
Methode serialisiert zu einem String. Das Belastung()
Methode deserialisiert von einem offenen dateiähnlichen Objekt. Das Ladungen()
Methode deserialisiert aus einem String.
Pickle unterstützt standardmäßig ein Textprotokoll, verfügt jedoch auch über ein Binärprotokoll, das effizienter, aber nicht lesbar ist (hilfreich beim Debuggen)..
So können Sie ein Python-Objektdiagramm mit beiden Protokollen zu einem String und einer Datei zusammenfassen.
cpickle as pickle pickle.dumps importieren (einfach) "(dp1 \ nS'text '\ np2 \ nS'string' \ np3 \ nsS'none '\ np4 \ nNSS'boolean' \ np5 \ nI5 \ nsS'number '\ np6 \ nF3.43999999999999 \ nsS'int_list '\ np7 \ n (lp8 \ nI1 \ naI2 \ naI3 \ nas. "pickle.dumps (einfach, Protokoll = pickle.HIGHEST_PROTOCOL)' \ x80 \ x02 q \ x01 \ x02U \ x06stringq \ x03U \ x04noneq \ x04NU \ x07boolean \ x88U \ x06numberq \ x05G @ \ x0b \ x85 \ x1e \ xb8Q \ xb8Q \ x85U \ x08int_list] q \ x07_04]
Die binäre Darstellung mag größer erscheinen, aber aufgrund ihrer Darstellung ist dies eine Illusion. Beim Sichern in eine Datei beträgt das Textprotokoll 130 Byte, während das binäre Protokoll nur 85 Byte umfasst.
pickle.dump (einfach, offen ('simple1.pkl', 'w')) pickle.dump (einfach, offen ('simple2.pkl', 'wb'), Protokoll = pickle.HIGHEST_PROTOCOL) ls -la sim *. * -rw-r - r-- 1 gigi staff 130 Mar 9 02:42 simple1.pkl -rw-r - 1 gigi staff 85 Mar 9 02:43 simple2.pkl
Das Abholen von einem String ist so einfach wie folgt:
x = pickle.loads ("(dp1 \ nS 'text' \ np2 \ nS'string '\ np3 \ nsS'none' \ np4 \ nNsS'boolean '\ np5 \ nI5 \ nS5'number' \ np3 \ np3 \ 39399999999999 \ nsS'int_list '\ np7 \ n (lp8 \ nI1 \ naI2 \ naI3 \ nas. ") Bestätigen Sie x == simple x = pickle.loads (' \ x80 \ x02) q \ x01 (U \ x04textq \ x02U \ x06stringq \ x03U \ x04noneq \ x04NU \ x07boolean \ x88U \ x06numberq \ x05G @ \ x0b \ x85 \ x1e \ xb8Q \ xb8Q \ x85U \ x08int_list] q \ x06intoLinate]
Beachten Sie, dass Pickle das Protokoll automatisch herausfinden kann. Es ist nicht erforderlich, auch für das binäre Protokoll ein Protokoll anzugeben.
Das Abholen aus einer Datei ist genauso einfach. Sie müssen nur eine offene Datei angeben.
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 ')) mach x == einfach
Laut Dokumentation soll man binäre Pickles mit dem 'rb'-Modus öffnen, aber wie man sieht, funktioniert es in beiden Richtungen.
Mal sehen, wie Pickle mit dem komplexen Objektdiagramm umgeht.
pickle.dumps (complex) "(dp1 \ nS'a '\ nccopy_reg \ n_reconstructor \ np2 \ n (hauptsächlich) \ nA \ np3 \ nc__builtin __ \ nobp \ np4 \ nNtRp5 \ n (dp6 \ nSsimple' \ nPsimple dp8 \ nS'text '\ np9 \ nS'string' \ np10 \ nsS'none '\ np11 \ nNsS'boolean' \ np12 \ nI12 \ nsS'number '\ np13 \ nF3.43999999999999 \ nsS'int_list' \ npS \ ns14 ' n (lp15 \ nI1 \ naI2 \ naI3 \ nassbsSwenn '\ np16 \ ncdatetime \ ndatetime \ np17 \ n (S' \\ x07 \\ xe0 \\ x03 \\ x07 \\ x07 \\ x00 \\ x00 \\ x00 \\ x00 \\ x00 \\ x00 '\ ntRp18 \ ns. "pickle.dumps (komplex, Protokoll = pickle.HIGHEST_PROTOCOL)' \ x80 \ x02 q \ x01 (U \ x01ac__main __ \ nA \ nq \ x02) \ x81q \ x03 q \ x04U \ x0uEinfachq \ x05 q \ x06 (U \ x04Textq \ x07U \ x06stringq \ x08U \ x04noneq \ tNU \ x07boolean \ x88U \ x06numberq \ n \ x0b \ x0b \ x04 \ x04nx q \ x0b (K \ x01K \ x02K \ x03eusbU \ x04whenq \ x0ccdatetime \ nq \ r \ n \ x07 \ x03 \ x07 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00eu \ x0eu .dump (komplex, offen ('complex1.pkl', 'w')) pickle.dump (komplex, offen ('complex2.pkl', 'wb'), Protokoll = pickle.HIGHEST_PROTOCOL) ls -la comp *. * -rw-r - r-- 1 Gigi-Stab 327 9. März 02:58 complex1.pkl -rw-r - r-- 1 Gigi-Stab 171 09. März 02:58 complex2.pkl
Die Effizienz des binären Protokolls ist bei komplexen Objektgraphen sogar noch größer.
JSON (JavaScript Object Notation) ist seit Python 2.5 Bestandteil der Python-Standardbibliothek. Ich werde es an dieser Stelle als ein natives Format betrachten. Es handelt sich um ein textbasiertes Format und ist in Bezug auf die Objektserialisierung der inoffizielle König des Webs. Das Typsystem modelliert natürlich JavaScript, daher ist es ziemlich begrenzt.
Lassen Sie uns die einfachen und komplexen Objektdiagramme serialisieren und deserialisieren und sehen, was passiert. Die Schnittstelle ist fast identisch mit der Pickle-Schnittstelle. Du hast dump ()
, Dumps ()
, Belastung()
, und Ladungen()
Funktionen. Es gibt jedoch keine Protokolle zur Auswahl, und es gibt viele optionale Argumente, um den Prozess zu steuern. Beginnen wir einfach mit dem Ablegen des einfachen Objektdiagramms ohne besondere Argumente:
import json print json.dumps (einfach) "text": "string", "none": null, "boolean": true, "number": 3.44, "int_list": [1, 2, 3]
Die Ausgabe sieht gut lesbar aus, es gibt jedoch keine Einrückung. Bei einem größeren Objektdiagramm kann dies ein Problem sein. Lassen Sie uns die Ausgabe einrücken:
print json.dumps (einfach, Einzug = 4) "text": "string", "none": null, "boolean": "true", "number": 3.44, "int_list": [1, 2, 3]
Das sieht viel besser aus. Gehen wir weiter zum komplexen Objektdiagramm.
json.dumps (komplex) ------------------------------------------ ------------------------------- TypeError Traceback (letzter Aufruf zuletzt)im () ----> 1 json.dumps (komplex) /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.pyc in Dumps (obj, skipkeys, sure_ascii, check_circular, allow_nan, cls, Einzug, Trennzeichen, Kodierung, Standardwert, sort_keys, ** kw) 241 cls ist Keine und Einzug ist Keine und Trennungen sind Keine und 242 Kodierung == 'utf-8' und default ist None und nicht sort_keys und nicht kw): -> 243 return _default_encoder.encode (obj) 244, wenn cls None ist: 245 cls = JSONEncoder /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework /Versions/2.7/lib/python2.7/json/encoder.pyc in encode (self, o) 205 # Ausnahmen sind nicht so detailliert. Der Listenaufruf sollte in etwa 206 # sein, was der PySequence_Fast entspricht, die ".join ()" tun würde. -> 207 chunks = self.iterencode (o, _one_shot = true) 208, falls nicht eine Instanz (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 (Markierungen, _default, _encoder, _flatstr, / usr / local / Cellar / python / 2.7.10 / Frameworks / Python.framework / Versionen / 2.7 / lib / python2.7 / json / encoder.pyc in der Standardeinstellung (selbst, o) 182 183 "" "-> 184 raise TypeError ( repr (o) + "ist nicht JSON serialisierbar") 185 186 def encode (self, o): TypeError: <__main__.A object at 0x10f367cd0> ist nicht JSON serialisierbar
Whoa! Das sieht überhaupt nicht gut aus. Was ist passiert? Die Fehlermeldung lautet, dass das A-Objekt nicht in JSON serialisierbar ist. Denken Sie daran, dass JSON ein sehr eingeschränktes Typsystem hat und benutzerdefinierte Klassen nicht automatisch serialisiert werden kann. Um dies zu erreichen, müssen Sie die JSONEncoder-Klasse, die vom json-Modul verwendet wird, subclassieren und das implementieren Standard()
Das wird aufgerufen, wenn der JSON-Encoder ein Objekt ausführt, das nicht serialisiert werden kann.
Die Aufgabe des benutzerdefinierten Encoders besteht darin, ihn in einen Python-Objektgraphen umzuwandeln, den der JSON-Encoder codieren kann. In diesem Fall haben wir zwei Objekte, die eine spezielle Kodierung erfordern: das Terminzeit
Objekt und die A-Klasse. Der folgende Encoder erledigt die Arbeit. Jedes spezielle Objekt wird in ein konvertiert Dikt
Dabei ist der Schlüssel der Name des von Dundern umgebenen Typs (doppelte Unterstriche). Dies wird für die Dekodierung wichtig sein.
from datetime import datetime import json-Klasse CustomEncoder (json.JSONEncoder): def default (selbst, o): wenn isinstance (o, datetime): return '__datetime__': o.replace (microsecond = 0) .isoformat () return '__ __'. format (o .__ class __.__ name__): o .__ dict__
Versuchen wir es noch einmal mit unserem benutzerdefinierten Encoder:
serialized = json.dumps (komplex, Einrückung = 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"
Das ist schön. Der komplexe Objektgraph wurde ordnungsgemäß serialisiert, und die ursprünglichen Typinformationen der Komponenten wurden über die Schlüssel "__A__" und "__datetime__" beibehalten. Wenn Sie für Ihre Namen Dunder verwenden, müssen Sie eine andere Konvention einführen, um spezielle Typen zu kennzeichnen.
Dekodieren wir den komplexen Objektgraph.
> deserialized = json.loads (serialized)> deserialized == complex False
Hmmm, die Deserialisierung hat funktioniert (keine Fehler), aber sie unterscheidet sich von der ursprünglichen komplexen Objektgrafik, die wir serialisiert haben. Irgendwas stimmt nicht. Schauen wir uns den deserialisierten Objektgraphen an. Ich benutze die pprint
Funktion des pprint
Modul für schönes Drucken.
> aus pprint import pprint> pprint (deserialisiert) u'a ': u' __ A__ ': u'simple': u'boolean ': wahr, 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. Das Problem ist, dass das Json-Modul nichts über die A-Klasse oder gar das Standard-Datetime-Objekt weiß. Es deserialisiert lediglich alles standardmäßig für das Python-Objekt, das seinem Typsystem entspricht. Um wieder zu einem umfangreichen Python-Objektdiagramm zu gelangen, benötigen Sie eine benutzerdefinierte Dekodierung.
Es ist keine benutzerdefinierte Decoder-Unterklasse erforderlich. Das Belastung()
und Ladungen()
Funktionen stellen den Parameter "object_hook" bereit, mit dem Sie eine benutzerdefinierte Funktion bereitstellen können, mit der Diagramme in Objekte konvertiert werden.
def decode_object (o): Wenn '__A__' in o: a = A () a .__ dict __. update (o ['__ A__'])) wird ein elif '__datetime__' in o zurückgegeben: return datetime.strptime (o ['__ datetime__') ], '% Y-% m-% dT% H:% M:% S') ergibt o
Dekodieren wir mit der decode_object ()
Funktion als Parameter für die Ladungen()
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)> deserialized == complex True
In Teil 1 dieses Tutorials haben Sie das allgemeine Konzept der Serialisierung und Deserialisierung von Python-Objekten kennengelernt und die Ein- und Ausläufe der Python-Serialisierung mithilfe von Pickle und JSON untersucht.
In Teil zwei erfahren Sie mehr über YAML, die Leistungs- und Sicherheitsbedenken sowie eine kurze Übersicht über weitere Serialisierungsschemata.
Lernen Sie Python mit unserem kompletten Python-Tutorial, egal ob Sie gerade erst anfangen oder ein erfahrener Programmierer sind, der neue Fähigkeiten erlernen möchte.