Serialisering og deserialisering av Python-objekter Del 2

Dette er del to av en veiledning om serialisering og deserialisering av Python-objekter. I første del lærte du det grunnleggende og deretter dovet inn i innsatsene i Pickle og JSON. 

I denne delen skal du utforske YAML (sørg for å ha det kjørende eksemplet fra første del), diskutere ytelses- og sikkerhetshensyn, få en gjennomgang av flere serialiseringsformater, og endelig lær hvordan du velger riktig ordning.

YAML

YAML er mitt favorittformat. Det er et menneske-vennlig dataserialiseringsformat. I motsetning til Pickle og JSON, er det ikke en del av Python standardbiblioteket, så du må installere det:

pip installere yaml

Yaml-modulen har bare laste() og dump () funksjoner. Som standard arbeider de med strenger som laster() og dumper(), men kan ta et andre argument, som er en åpen strøm og deretter kan dumpe / laste til / fra filer.

import yaml print yaml.dump (enkel) boolsk: sann int_list: [1, 2, 3] ingen: null nummer: 3,44 tekst: streng

Legg merke til hvordan lesbar YAML er sammenlignet med Pickle eller JSON. Og nå for den kuleste delen om YAML: Det forstår Python-objekter! Ingen behov for egendefinerte kodere og dekodere. Her er kompleks serialisering / deserialisering ved hjelp av YAML:

> serialisert = yaml.dump (kompleks)> print serialisert a: !! python / objekt: __ main __. En enkel: boolsk: sann int_list: [1, 2, 3] ingen: null nummer: 3.44 tekst: streng når: 2016- 03-07 00:00:00> deserialized = yaml.load (serialisert)> deserialisert == kompleks True

Som du kan se, har YAML sin egen notasjon for å merke Python-objekter. Utgangen er fortsatt svært menneskelig lesbar. Datetime-objektet krever ingen spesiell merking fordi YAML ibo støtter datetimeobjekter. 

Opptreden

Før du begynner å tenke på ytelse, må du tenke hvis ytelsen er en bekymring i det hele tatt. Hvis du serialiserer / deserialiserer en liten mengde data relativt sjeldent (for eksempel å lese en config-fil i begynnelsen av et program), er ytelsen ikke en bekymring, og du kan fortsette.

Men, forutsatt at du profilerte systemet ditt og oppdaget at serialisering og / eller deserialisering forårsaker ytelsesproblemer, her er de tingene du skal adressere.

Det er to aspekter for ytelse: Hvor fort kan du serialisere / deserialisere, og hvor stor er serialisert representasjon?

For å teste ytelsen til de ulike serialiseringsformatene, lager jeg en stor datastruktur og serialiserer / deserialiserer den ved hjelp av Pickle, YAML og JSON. De stor Data listen inneholder 5000 komplekse objekter.

big_data = [dict (a = enkel, når = datetime.now (). erstatt (mikrosekund = 0)) for jeg i rekkevidde (5000)]

pickle

Jeg bruker IPython her for å være praktisk % timeit magisk funksjon som måler utførelsestider.

importere cPickle som pickle I [190]:% timeit serialized = pickle.dumps (big_data) 10 looper, best av 3: 51 ms per loop I [191]:% timeit deserialized = pickle.loads (serialisert) 10 looper, best av 3: 24,2 ms per loop I [192]: deserialisert == big_data Ut [192]: True i [193]: len (serialisert) Ut [193]: 747328

Standardinntaket tar 83,1 millisekunder til serialisering og 29,2 millisekunder for deserialisering, og den serialiserte størrelsen er 747,328 byte.

La oss prøve den høyeste protokollen.

I [195]:% timeit serialized = pickle.dumps (big_data, protocol = pickle.HIGHEST_PROTOCOL) 10 løkker, best av 3: 21,2 ms per loop I [196]:% timeit deserialized = pickle.loads (serialiserte) 10 looper, best av 3: 25,2 ms per loop I [197]: len (serialisert) Out [197]: 394350

Interessante resultater. Serialiseringstiden gikk ned til bare 21,2 millisekunder, men deserialiseringstiden økte litt til 25,2 millisekunder. Den serialiserte størrelsen krympet vesentlig til 394 350 byte (52%).

JSON

I [253]% timeit serialized = json.dumps (big_data, cls = CustomEncoder) 10 sløyfer, best av 3: 34,7 ms per loop I [253]% timeit deserialized = json.loads (serialisert, object_hook = decode_object) best av 3: 148 ms per loop I [255]: len (serialisert) Ut [255]: 730000

Ok. Ytelsen ser ut til å være litt verre enn Pickle for koding, men mye, mye verre for dekoding: 6 ganger langsommere. Hva skjer? Dette er en artefakt av object_hook funksjon som må løpe for hver ordbok for å sjekke om den trenger å konvertere den til et objekt. Kjører uten objektkrok er mye raskere.

% timeit deserialized = json.loads (serialisert) 10 looper, best av 3: 36,2 ms per loop

Leksjonen her er at når serialisering og deserialisering til JSON, bør du nøye vurdere noen tilpassede kodinger fordi de kan ha stor innvirkning på ytelsen.

YAML

I [293]:% timeit serialized = yaml.dump (big_data) 1 sløyfer, best av 3: 1,22 s per sløyfe I [294]:% timeit deserialized = yaml.load (serialisert) 1 sløyfer, best av 3: 2.03 s per slynge I [295]: len (serialisert) Ut [295]: 200091

Ok. YAML er virkelig, veldig treg. Men vær oppmerksom på noe interessant: Serialisert størrelse er bare 200,091 byte. Mye bedre enn både Pickle og JSON. La oss se inne i ekte hurtig:

I [300]: skriv ut serialisert [: 211] - a: & id001 booleansk: sant int_list: [1, 2, 3] ingen: null nummer: 3,44 tekst: streng når: 2016-03-13 00:11:44 - a : * id001 når: 2016-03-13 00:11:44 - a: * id001 når: 2016-03-13 00:11:44

YAML er veldig smart her. Det identifiserte at alle 5.000 diktene deler samme verdi for "a" -tasten, slik at den bare lagrer den en gang og refererer til den ved hjelp av * id001 for alle objekter.

Sikkerhet

Sikkerhet er ofte en kritisk bekymring. Pickle og YAML, i kraft av å bygge Python-objekter, er sårbare for kodeksjonsangrep. En smart formatert fil kan inneholde vilkårlig kode som vil bli utført av Pickle eller YAML. Det er ikke nødvendig å være foruroliget. Dette er av design og er dokumentert i Pickles dokumentasjon:

Advarsel: Pickle-modulen er ikke ment å være sikker mot feil eller skadelig konstruert data. Ikke unpickle data mottatt fra en usikker eller uautorisert kilde.

I tillegg til i YAMLs dokumentasjon:

Advarsel: Det er ikke trygt å ringe yaml.load med data mottatt fra en usikker kilde! yaml.load er like kraftig som pickle.load og det kan også kalle noen Python-funksjon.

Du trenger bare å forstå at du ikke skal laste serialiserte data mottatt fra usikre kilder ved hjelp av Pickle eller YAML. JSON er OK, men igjen hvis du har egendefinerte kodere / dekodere enn du kanskje blir utsatt for.

Yaml-modulen gir yaml.safe_load () funksjon som vil laste bare enkle objekter, men da mister du mye YAMLs strøm og kanskje velger å bare bruke JSON.

Andre formater

Det finnes mange andre serialiseringsformater tilgjengelig. Her er noen av dem.

Protobuf

Protobuf- eller protokollbuffere er Googles datautvekslingsformat. Det er implementert i C ++, men har Python-bindinger. Den har et sofistikert skjema og pakker data effektivt. Veldig kraftig, men ikke veldig lett å bruke.

MessagePack

MessagePack er et annet populært serialiseringsformat. Det er også binært og effektivt, men i motsetning til Protobuf krever det ikke et skjema. Den har et type system som ligner på JSON, men litt rikere. Nøkler kan være alle typer, og ikke bare strenger og ikke-UTF8-strenger støttes.

CBOR

CBOR står for konsistent binær objektrepresentasjon. Igjen støtter den JSON datamodellen. CBOR er ikke så godt kjent som Protobuf eller MessagePack, men er interessant av to grunner: 

  1. Det er en offisiell Internett-standard: RFC 7049.
  2. Det ble designet spesielt for tingenes Internett (IoT).

Hvordan velge?

Dette er det store spørsmålet. Med så mange alternativer, hvordan velger du? La oss vurdere de ulike faktorene som bør tas i betraktning:

  1. Skal det serialiserte formatet være menneskelig lesbart og / eller menneskelig redigerbart?
  2. Serialisert innhold vil bli mottatt fra usikre kilder?
  3. Er serialisering / deserialisering en ytelse flaskehals?
  4. Serialiserte data må byttes med ikke-Python-miljøer?

Jeg gjør det veldig enkelt for deg og dekker flere vanlige scenarier og hvilket format jeg anbefaler for hver enkelt:

Automatisk lagring av lokal tilstand for et pythonprogram

Bruk pickle (cPickle) her med HIGHEST_PROTOCOL. Det er raskt, effektivt og kan lagre og laste inn de fleste Python objekter uten noen spesiell kode. Den kan også brukes som en lokal vedvarende cache.

Konfigurasjonsfiler

Definitivt YAML. Ingenting slår sin enkelhet for alt mennesker trenger å lese eller redigere. Den er brukt av Ansible og mange andre prosjekter. I enkelte situasjoner kan du foretrekke å bruke rett Python-moduler som konfigurasjonsfiler. Dette kan være det riktige valget, men da er det ikke serialisering, og det er egentlig en del av programmet og ikke en separat konfigurasjonsfil.

Web-APIer

JSON er den klare vinneren her. Disse dagene blir web-APIer ofte brukt av JavaScript-webprogrammer som snakker JSON nativt. Enkelte web-APIer kan returnere andre formater (for eksempel csv for tette tabellresultater), men jeg vil hevde at du kan pakke csv-data til JSON med minimal overhead (ikke nødvendig å gjenta hver rad som et objekt med alle kolonnens navn). 

High-Volume / Low-Latency Storskala kommunikasjon

Bruk en av de binære protokollene: Protobuf (hvis du trenger et skjema), MessagePack eller CBOR. Kjør dine egne tester for å verifisere ytelsen og den representative kraften til hvert alternativ.

Konklusjon

Serialisering og deserialisering av Python-objekter er et viktig aspekt av distribuerte systemer. Du kan ikke sende Python-objekter direkte over ledningen. Du må ofte samarbeide med andre systemer implementert på andre språk, og noen ganger vil du bare lagre tilstanden til programmet ditt i vedvarende lagring. 

Python kommer med flere serialiseringsordninger i sitt standardbibliotek, og mange flere er tilgjengelige som tredjepartsmoduler. Å være klar over alle alternativene og fordelene og ulemperne til hver enkelt vil la deg velge den beste metoden for situasjonen din.