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 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.
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)]
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%).
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.
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 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.
Det finnes mange andre serialiseringsformater tilgjengelig. Her er noen av dem.
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 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 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:
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:
Jeg gjør det veldig enkelt for deg og dekker flere vanlige scenarier og hvilket format jeg anbefaler for hver enkelt:
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.
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.
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).
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.
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.