Dit is deel twee van een tutorial over het serialiseren en deserialiseren van Python-objecten. In deel een heb je de basis geleerd en vervolgens in het reilen en zeilen van Pickle en JSON gedoken.
In dit deel verken je YAML (zorg dat je het voorbeeld uit deel een hebt), bespreek prestaties en veiligheidsoverwegingen, krijg een beoordeling van aanvullende serialisatieformaten en leer uiteindelijk hoe je het juiste schema kiest.
YAML is mijn favoriete formaat. Het is een mensvriendelijke data serialisatie-indeling. In tegenstelling tot Pickle en JSON, maakt het geen deel uit van de standaardbibliotheek van Python, dus u moet het installeren:
pip yaml installeren
De yaml-module heeft alleen laden()
en dump ()
functies. Standaard werken ze met tekenreeksen zoals belasting ()
en stortplaatsen ()
, maar kan een tweede argument nemen, wat een open stream is en vervolgens kan dumpen / laden van / naar bestanden.
import yaml print yaml.dump (simple) boolean: true int_list: [1, 2, 3] geen: nulnummer: 3.44 tekst: string
Merk op hoe leesbare YAML wordt vergeleken met Pickle of zelfs JSON. En nu voor het coolste deel over YAML: het begrijpt Python-objecten! Geen behoefte aan aangepaste encoders en decoders. Hier is de complexe serialisatie / deserialisatie met YAML:
> serialized = yaml.dump (complex)> serialized a: !! python / object: __ main __. A simple: boolean: true int_list: [1, 2, 3] none: null number: 3.44 text: string when: 2016- 03-07 00:00:00> deserialized = yaml.load (geserialiseerd)> deserialized == complex True
Zoals u kunt zien, heeft YAML zijn eigen notatie om Python-objecten te labelen. De output is nog steeds erg menselijk leesbaar. Voor het datetime-object is geen speciale tagging vereist, omdat YAML inherent datetime-objecten ondersteunt.
Voordat je gaat nadenken over prestaties, moet je nadenken of de prestaties überhaupt een probleem zijn. Als je een kleine hoeveelheid gegevens relatief niet vaak serialiseert / deserialiseert (bijvoorbeeld het lezen van een configuratiebestand aan het begin van een programma), dan is prestatie niet echt een probleem en kun je verder gaan.
Maar als u ervan uitgaat dat u uw systeem hebt geprofileerd en hebt ontdekt dat serialisatie en / of deserialisatie problemen met de prestaties veroorzaken, zijn dit de zaken die u moet aanpakken.
Het zijn twee aspecten voor de uitvoering: hoe snel kan je serialiseren / deserialiseren, en hoe groot is de geserialiseerde weergave?
Om de prestaties van de verschillende serialisatieformaten te testen, zal ik een vrij grote gegevensstructuur maken en deze serialiseren / deserialiseren met behulp van Pickle, YAML en JSON. De big_data
lijst bevat 5.000 complexe objecten.
big_data = [dict (a = simple, when = datetime.now (). replace (microsecond = 0)) voor i in bereik (5000)]
Ik zal hier IPython gebruiken omdat het handig is % timeit
magische functie die uitvoertijden meet.
import cPickle as pickle In [190]:% timeit serialized = pickle.dumps (big_data) 10 loops, best of 3: 51 ms per lus In [191]:% timeit gedeserialiseerd = pickle.loads (geserialiseerd) 10 loops, beste van 3: 24,2 ms per lus In [192]: gedeserialiseerd == big_data Uit [192]: waar In [193]: len (geserialiseerd) Uit [193]: 747328
De standaardbeitel neemt 83,1 milliseconden in beslag om te serialiseren en 29,2 milliseconden om te deserialiseren, en de geserialiseerde grootte is 747,328 bytes.
Laten we het proberen met het hoogste protocol.
In [195]:% timeit serialized = pickle.dumps (big_data, protocol = pickle.HIGHEST_PROTOCOL) 10 loops, beste van 3: 21,2 ms per lus In [196]:% timeit gedeserialiseerd = pickle.loads (geserialiseerd) 10 lussen, beste van 3: 25,2 ms per lus In [197]: len (geserialiseerd) Uit [197]: 394350
Interessante resultaten. De serialisatietijd kromp tot slechts 21,2 milliseconden, maar de deserialisatietijd nam iets toe tot 25,2 milliseconden. De geserialiseerde grootte kromp aanzienlijk tot 394.350 bytes (52%).
In [253]% timeit serialized = json.dumps (big_data, cls = CustomEncoder) 10 loops, best van 3: 34,7 ms per lus In [253]% timeit gedeserialiseerd = json.loads (serialized, object_hook = decode_object) 10 loops, beste van 3: 148 ms per lus In [255]: len (geserialiseerd) Uit [255]: 730000
OK. Prestaties lijken iets slechter dan Pickle voor codering, maar veel, veel slechter voor decodering: 6 keer langzamer. Wat gebeurd er? Dit is een artefact van de object_hook
functie die voor elk woordenboek moet worden uitgevoerd om te controleren of het moet worden geconverteerd naar een object. Hardlopen zonder de objecthaak is veel sneller.
% timeit gedeserialiseerd = json.loads (geserialiseerd) 10 lussen, het beste van 3: 36,2 ms per lus
De les hier is dat bij het serialiseren en deserialiseren naar JSON, zeer zorgvuldig rekening moet worden gehouden met aangepaste coderingen, omdat deze mogelijk een grote invloed hebben op de algehele prestaties..
In [293]:% timeit serialized = yaml.dump (big_data) 1 loops, beste van 3: 1,22 s per lus In [294]:% timeit gedeserialiseerd = yaml.load (geserialiseerd) 1 loops, best of 3: 2.03 s per lus In [295]: len (geserialiseerd) Uit [295]: 200091
OK. YAML is echt, erg traag. Maar let op iets interessants: de geserialiseerde grootte is slechts 200.091 bytes. Veel beter dan zowel Pickle als JSON. Laten we snel naar binnen kijken:
In [300]: serienummer afdrukken [: 211] - a: & id001 boolean: true int_list: [1, 2, 3] geen: nulnummer: 3.44 tekst: string when: 2016-03-13 00:11:44 - a : * id001 wanneer: 2016-03-13 00:11:44 - a: * id001 wanneer: 2016-03-13 00:11:44
YAML is hier heel slim. Het identificeerde dat alle 5.000 dictaten dezelfde waarde delen voor de 'a'-sleutel, dus slaat het slechts één keer op en verwijst het naar het gebruik ervan * id001
voor alle objecten.
Beveiliging is vaak een kritieke zorg. Groenten in het zuur en YAML zijn door het bouwen van Python-objecten kwetsbaar voor code-uitvoeringsaanvallen. Een slim geformatteerd bestand kan willekeurige code bevatten die wordt uitgevoerd door Pickle of YAML. Het is niet nodig om gealarmeerd te zijn. Dit is door het ontwerp en is gedocumenteerd in de documentatie van Pickle:
Waarschuwing: de pickle-module is niet bedoeld als beveiligd tegen onjuiste of kwaadwillig geconstrueerde gegevens. Nooit ontkoppelen gegevens ontvangen van een niet-vertrouwde of niet-geverifieerde bron.
Evenals in de documentatie van YAML:
Waarschuwing: het is niet veilig om yaml.load te bellen met gegevens die zijn ontvangen van een niet-vertrouwde bron! yaml.load is net zo krachtig als pickle.load en kan dus elke Python-functie oproepen.
U hoeft alleen maar te begrijpen dat u geserialiseerde gegevens die zijn ontvangen van niet-vertrouwde bronnen niet moet laden met Pickle of YAML. JSON is OK, maar nogmaals, als je aangepaste encoders / decoders hebt, dan kun je ook worden blootgesteld.
De yaml-module biedt de yaml.safe_load ()
functie die alleen eenvoudige objecten laadt, maar dan verlies je veel YAML's kracht en misschien kies je ervoor om gewoon JSON te gebruiken.
Er zijn veel andere serialisatieformaten beschikbaar. Hier zijn er een paar.
Protobuf of protocolbuffers is het gegevensuitwisselingsformaat van Google. Het is geïmplementeerd in C ++ maar heeft Python-bindingen. Het heeft een geavanceerd schema en pakt gegevens efficiënt in. Zeer krachtig, maar niet erg gemakkelijk te gebruiken.
MessagePack is een ander populair serialisatieformaat. Het is ook binair en efficiënt, maar in tegenstelling tot Protobuf heeft het geen schema nodig. Het heeft een type systeem dat vergelijkbaar is met JSON, maar een beetje rijker. Sleutels kunnen van elk type zijn en niet alleen tekenreeksen en niet-UTF8-reeksen worden ondersteund.
CBOR staat voor Concise Binary Object Representation. Nogmaals, het ondersteunt het JSON-gegevensmodel. CBOR is niet zo bekend als Protobuf of MessagePack, maar is om twee redenen interessant:
Dit is de grote vraag. Met zoveel opties, hoe kies je? Laten we eens kijken naar de verschillende factoren waarmee rekening moet worden gehouden:
Ik zal het heel gemakkelijk voor je maken en een aantal veelvoorkomende scenario's behandelen en welk formaat ik voor elke oplossing aanbeveel:
Gebruik pickle (cPickle) hier met de HIGHEST_PROTOCOL
. Het is snel en efficiënt en kan de meeste Python-objecten opslaan en laden zonder speciale code. Het kan ook worden gebruikt als een lokale persistente cache.
Absoluut YAML. Er gaat niets boven zijn eenvoud voor wat mensen moeten lezen of bewerken. Het is met succes gebruikt door Ansible en vele andere projecten. In sommige situaties kunt u de voorkeur geven aan rechte Python-modules als configuratiebestanden. Dit is misschien de juiste keuze, maar het is geen serialisatie en het maakt echt deel uit van het programma en geen afzonderlijk configuratiebestand.
JSON is hier de duidelijke winnaar. Tegenwoordig worden web-API's het vaakst gebruikt door JavaScript-webtoepassingen die native JSON spreken. Sommige web-API's kunnen andere indelingen teruggeven (bijvoorbeeld csv voor compacte tabellarische resultatensets), maar ik zou stellen dat u csv-gegevens in JSON kunt verpakken met minimale overhead (het is niet nodig om elke rij als een object met alle kolomnamen te herhalen).
Gebruik een van de binaire protocollen: Protobuf (als u een schema nodig hebt), MessagePack of CBOR. Voer uw eigen tests uit om de prestaties en de representatieve kracht van elke optie te verifiëren.
Serialisatie en deserialisatie van Python-objecten is een belangrijk aspect van gedistribueerde systemen. Je kunt Python-objecten niet rechtstreeks over de draad verzenden. U moet vaak samenwerken met andere systemen die in andere talen zijn geïmplementeerd en soms wilt u gewoon de status van uw programma opslaan in permanente opslag.
Python wordt geleverd met verschillende serialisatieschema's in de standaardbibliotheek en veel meer zijn beschikbaar als modules van derden. Door je bewust te zijn van alle opties en de voor- en nadelen van elke optie, kun je de beste methode voor je situatie kiezen.