Serialisierung und Deserialisierung von Python-Objekten Teil 2

Dies ist Teil zwei eines Tutorials zum Serialisieren und Deserialisieren von Python-Objekten. Im ersten Teil haben Sie die Grundlagen gelernt und sich dann mit den Besonderheiten von Pickle und JSON beschäftigt. 

In diesem Teil lernen Sie YAML (stellen Sie sicher, dass Sie ein laufendes Beispiel aus Teil 1 haben), diskutieren Sie Leistungs- und Sicherheitsaspekte, erhalten Sie einen Überblick über weitere Serialisierungsformate und erfahren Sie, wie Sie das richtige Schema auswählen.

YAML

YAML ist mein Lieblingsformat. Es ist ein menschenfreundliches Datenserialisierungsformat. Im Gegensatz zu Pickle und JSON ist es nicht Teil der Python-Standardbibliothek. Sie müssen es also installieren:

pip install yaml

Das yaml-Modul hat nur Belastung() und dump () Funktionen. Standardmäßig arbeiten sie mit Strings wie Ladungen() und Dumps (), kann jedoch ein zweites Argument annehmen, das ein offener Stream ist und dann in / aus Dateien abladen / laden kann.

import yaml print yaml.dump (einfach) boolean: true int_list: [1, 2, 3] none: Nullzahl: 3.44 Text: Zeichenfolge

Beachten Sie, wie lesbar YAML mit Pickle oder sogar JSON verglichen wird. Und jetzt zum coolsten Teil über YAML: Es versteht Python-Objekte! Keine benutzerdefinierten Encoder und Decoder erforderlich. Hier ist die komplexe Serialisierung / Deserialisierung mit YAML:

> serialized = yaml.dump (complex)> print serialized a: !! python / object: __ main __ Ein einfaches: boolean: true int_list: [1, 2, 3] none: Nullzahl: 3.44 Text: String bei: 2016- 03-07 00:00:00> deserialized = yaml.load (serialized)> deserialized == complex True

Wie Sie sehen, hat YAML eine eigene Notation, um Python-Objekte zu kennzeichnen. Die Ausgabe ist immer noch sehr gut lesbar. Das datetime-Objekt erfordert kein spezielles Tagging, da YAML von datetime-Objekten inhärent unterstützt wird. 

Performance

Bevor Sie über Leistung nachdenken, müssen Sie erst darüber nachdenken, ob Leistung überhaupt ein Problem ist. Wenn Sie eine kleine Datenmenge relativ selten serialisieren / deserialisieren (z. B. Lesen einer Konfigurationsdatei am Anfang eines Programms), ist die Leistung nicht wirklich ein Problem und Sie können weitermachen.

Wenn Sie jedoch davon ausgehen, dass Sie Ihr System profiliert haben und festgestellt haben, dass Serialisierung und / oder Deserialisierung zu Leistungsproblemen führen, sollten Sie Folgendes beachten.

Dies sind zwei Aspekte für die Leistung: Wie schnell können Sie serialisieren / deserialisieren und wie groß ist die serialisierte Darstellung?

Um die Leistung der verschiedenen Serialisierungsformate zu testen, erstelle ich eine umfangreiche Datenstruktur und serialisiert / deserialisiert sie mit Pickle, YAML und JSON. Das Große Daten Die Liste enthält 5.000 komplexe Objekte.

big_data = [dict (a = einfach, wenn = datetime.now (). replace (Mikrosekunde = 0)) für i im Bereich (5000)]

Essiggurke

Ich werde IPython hier für seine Bequemlichkeit verwenden % timeit magische Funktion, die Ausführungszeiten misst.

cPickle als Pickle importieren In [190]:% timeit serialized = pickle.dumps (big_data) 10 Schleifen, am besten von 3: 51 ms pro Schleife In [191]:% timeit deserialized = pickle.loads (serialisiert) 10 Schleifen, best of 3: 24,2 ms pro Schleife In [192]: deserialized == big_data Out [192]: True In [193]: len (serialisiert) Out [193]: 747328

Die Standardeinstellung für die Serialisierung beträgt 83,1 Millisekunden und die Deserialisierung 29,2 Millisekunden. Die serialisierte Größe beträgt 747.328 Byte.

Versuchen wir es mit dem höchsten Protokoll.

In [195]:% timeit serialized = pickle.dumps (big_data, protocol = pickle.HIGHEST_PROTOCOL) 10 Schleifen, am besten von 3: 21,2 ms pro Schleife In [196]:% timeit deserialized = pickle.loads (serialisiert) 10 Schleifen, Best of 3: 25,2 ms pro Schleife In [197]: len (serialisiert) Out [197]: 394350

Interessante Ergebnisse. Die Serialisierungszeit verringerte sich auf nur 21,2 Millisekunden, die Deserialisierungszeit erhöhte sich jedoch leicht auf 25,2 Millisekunden. Die serialisierte Größe schrumpfte signifikant auf 394.350 Bytes (52%)..

JSON

In [253]% timeit serialized = json.dumps (big_data, cls = CustomEncoder) 10 Schleifen, am besten von 3: 34,7 ms pro Schleife In [253]% timeit deserialized = json.loads (serialisiert, object_hook = decode_object) 10 Schleifen. Best of 3: 148 ms pro Schleife In [255]: len (serialisiert) Out [255]: 730000

OK. Die Performance scheint beim Codieren etwas schlechter als bei Pickle zu sein, beim Decodieren jedoch viel, viel schlechter: 6-mal langsamer. Was ist los? Dies ist ein Artefakt der object_hook Funktion, die für jedes Wörterbuch ausgeführt werden muss, um zu prüfen, ob es in ein Objekt konvertiert werden muss. Das Laufen ohne Objekthaken ist viel schneller.

% timeit deserialized = json.loads (serialized) 10 Schleifen, am besten von 3: 36,2 ms pro Schleife

Die Lektion hier ist, dass bei der Serialisierung und Deserialisierung in JSON alle benutzerdefinierten Kodierungen sorgfältig geprüft werden sollten, da sie die Gesamtleistung erheblich beeinflussen können.

YAML

In [293]:% timeit serialized = yaml.dump (big_data) 1 Schleifen, am besten von 3: 1,22 s pro Schleife In [294]:% timeit deserialized = yaml.load (serialisiert) 1 Schleifen, am besten 3: 2,03 s pro Schleife In [295]: len (serialisiert) Out [295]: 200091

OK. YAML ist wirklich sehr langsam. Beachten Sie jedoch etwas Interessantes: Die serialisierte Größe beträgt nur 200.091 Bytes. Viel besser als sowohl Pickle als auch JSON. Schauen wir uns schnell mal rein:

In [300]: print serialized [: 211] - a: & id001 boolean: true int_list: [1, 2, 3] none: Nullzahl: 3.44 Text: Zeichenfolge, wenn: 2016-03-13 00:11:44 - a : * id001 wann: 2016-03-13 00:11:44 - a: * id001 wann: 2016-03-13 00:11:44

YAML ist hier sehr klug. Es hat festgestellt, dass alle 5.000 Diktaturen den gleichen Wert für den 'a'-Schlüssel haben, also nur einmal gespeichert und unter Verwendung referenziert werden * id001 für alle Objekte.

Sicherheit

Sicherheit ist oft ein kritisches Anliegen. Pickle und YAML sind durch das Erstellen von Python-Objekten anfällig für Codeausführungsangriffe. Eine intelligent formatierte Datei kann beliebigen Code enthalten, der von Pickle oder YAML ausgeführt wird. Es besteht keine Notwendigkeit, alarmiert zu werden. Dies ist von Entwurf und ist in der Dokumentation von Pickle dokumentiert:

Warnung: Das Pickle-Modul soll nicht vor fehlerhaften oder in böswilliger Absicht erstellten Daten geschützt sein. Entpacken Sie niemals Daten aus einer nicht vertrauenswürdigen oder nicht authentifizierten Quelle.

Sowie in der YAML-Dokumentation:

Warnung: Es ist nicht sicher, yaml.load mit Daten aus einer nicht vertrauenswürdigen Quelle anzurufen! yaml.load ist so leistungsfähig wie pickle.load und kann daher jede Python-Funktion aufrufen.

Sie müssen nur wissen, dass Sie keine serialisierten Daten laden dürfen, die von nicht vertrauenswürdigen Quellen mit Pickle oder YAML empfangen wurden. JSON ist in Ordnung, aber auch wenn Sie benutzerdefinierte Encoder / Decoder haben, können Sie auch ausgesetzt sein.

Das yaml-Modul liefert die yaml.safe_load () Funktion, die nur einfache Objekte lädt, aber dann verlieren Sie eine Menge an YAMLs Leistung und entscheiden sich möglicherweise für die Verwendung von JSON.

Andere Formate

Es gibt viele andere Serialisierungsformate. Hier sind einige davon.

Protobuf

Protobuf oder Protokollpuffer ist das Datenaustauschformat von Google. Es ist in C ++ implementiert, verfügt jedoch über Python-Bindungen. Es verfügt über ein ausgefeiltes Schema und packt Daten effizient. Sehr leistungsfähig, aber nicht sehr einfach zu bedienen.

MessagePack

MessagePack ist ein weiteres beliebtes Serialisierungsformat. Es ist auch binär und effizient, aber im Gegensatz zu Protobuf ist kein Schema erforderlich. Es hat ein Typensystem, das JSON ähnelt, aber etwas reicher ist. Schlüssel können von jedem Typ sein. Es werden nicht nur Strings und Nicht-UTF8-Strings unterstützt.

CBOR

CBOR steht für Concise Binary Object Representation. Wieder unterstützt es das JSON-Datenmodell. CBOR ist nicht so bekannt wie Protobuf oder MessagePack, ist aber aus zwei Gründen interessant: 

  1. Es ist ein offizieller Internetstandard: RFC 7049.
  2. Es wurde speziell für das Internet der Dinge (IoT) entwickelt..

Wie man wählt?

Das ist die große Frage. Wie wählen Sie bei so vielen Optionen aus? Betrachten wir die verschiedenen Faktoren, die berücksichtigt werden sollten:

  1. Sollte das serialisierte Format für Menschen lesbar und / oder für Menschen editierbar sein?
  2. Wird serialisierter Inhalt von nicht vertrauenswürdigen Quellen empfangen?
  3. Ist die Serialisierung / Deserialisierung ein Leistungsengpass?
  4. Müssen serialisierte Daten mit Nicht-Python-Umgebungen ausgetauscht werden??

Ich werde es Ihnen sehr leicht machen und einige gängige Szenarien behandeln und welches Format ich für jedes Szenario empfehle:

Lokalen Status eines Python-Programms automatisch speichern

Verwenden Sie hier Pickle (cPickle) mit dem HIGHEST_PROTOCOL. Es ist schnell, effizient und kann die meisten Python-Objekte ohne besonderen Code speichern und laden. Es kann auch als lokaler permanenter Cache verwendet werden.

Konfigurationsdateien

Auf jeden Fall YAML. Nichts ist einfacher als alles, was Menschen zum Lesen oder Bearbeiten benötigen. Es wird erfolgreich von Ansible und vielen anderen Projekten verwendet. In einigen Situationen ziehen Sie es vor, gerade Python-Module als Konfigurationsdateien zu verwenden. Dies kann die richtige Wahl sein, aber dann ist es keine Serialisierung, und es ist wirklich Teil des Programms und keine separate Konfigurationsdatei.

Web-APIs

JSON ist hier der klare Gewinner. Heutzutage werden Web-APIs am häufigsten von JavaScript-Webanwendungen verwendet, die nativ JSON sprechen. Einige Web-APIs geben möglicherweise andere Formate zurück (z. B. csv für dichte tabellarische Ergebnismengen). Ich würde jedoch argumentieren, dass Sie csv-Daten mit minimalem Aufwand in JSON packen können (Sie müssen nicht jede Zeile als Objekt mit allen Spaltennamen wiederholen).. 

Kommunikation mit großem Volumen und geringer Latenzzeit

Verwenden Sie eines der Binärprotokolle: Protobuf (wenn Sie ein Schema benötigen), MessagePack oder CBOR. Führen Sie Ihre eigenen Tests durch, um die Leistung und die repräsentative Leistungsfähigkeit jeder Option zu überprüfen.

Fazit

Die Serialisierung und Deserialisierung von Python-Objekten ist ein wichtiger Aspekt verteilter Systeme. Sie können Python-Objekte nicht direkt über die Leitung senden. Sie müssen häufig mit anderen Systemen zusammenarbeiten, die in anderen Sprachen implementiert sind, und manchmal möchten Sie nur den Status Ihres Programms in einem permanenten Speicher speichern. 

Python verfügt über mehrere Serialisierungsschemata in seiner Standardbibliothek. Viele weitere sind als Module von Drittanbietern verfügbar. Wenn Sie alle Möglichkeiten und Vor- und Nachteile kennen, können Sie die für Ihre Situation beste Methode wählen.