Serialisering och deserialisering av Python-objekt Del 2

Detta är del två av en handledning om serialisering och deserialisering av Python-objekt. I del ett lärde du dig grunderna och sedan dyker in i Innehållet i Pickle och JSON. 

I den här delen ska du utforska YAML (se till att du har körprocessen från första delen), diskutera prestations- och säkerhetshänsyn, få en översyn av ytterligare serialiseringsformat och äntligen lära dig hur du väljer rätt ordning.

YAML

YAML är mitt favoritformat. Det är ett människavänligt dataserialiseringsformat. Till skillnad från Pickle och JSON, ingår det inte i Python standardbiblioteket, så du måste installera det:

pip installera yaml

Yaml-modulen har bara ladda() och dumpa() funktioner. Som standard arbetar de med strängar som massor() och soptippar (), men kan ta ett andra argument, vilket är en öppen ström och sedan kan dumpa / ladda till / från filer.

importera yaml print yaml.dump (enkelt) booleskt: sant int_list: [1, 2, 3] none: null nummer: 3.44 text: string

Observera hur läsbar YAML jämförs med Pickle eller JSON. Och nu för den coolaste delen om YAML: det förstår Python-objekt! Inget behov av anpassade kodare och avkodare. Här är komplex serialisering / deserialisering med YAML:

> serialized = yaml.dump (komplex)> skriv serialiserad a: !! python / object: __ main __. En enkel: booleska: sant int_list: [1, 2, 3] none: null nummer: 3.44 text: sträng när: 2016- 03-07 00:00:00> deserialized = yaml.load (serialized)> deserialized == complex True

Som du kan se har YAML sin egen notering för att märka Python-objekt. Utgången är fortfarande mycket mänsklig läsbar. Datetime-objektet kräver ingen särskild märkning eftersom YAML i sig stöder datetime-objekt. 

Prestanda

Innan du börjar tänka på prestanda måste du tänka om prestanda är en oro alls. Om du serialiserar / deserialiserar en liten mängd data relativt sällan (t ex läser en konfigurationsfil i början av ett program) är prestanda inte riktigt ett problem och du kan gå vidare.

Men, förutsatt att du profilerade ditt system och upptäckte att serialisering och / eller deserialisering orsakar prestandafrågor, här är de saker som ska adresseras.

Det är två aspekter för prestanda: hur snabbt kan du serialisera / deserialisera, och hur stor är serialiserad representation?

För att testa prestanda för de olika serialiseringsformaten skapar jag en stor datastruktur och serialiserar / deserialiserar den med hjälp av Pickle, YAML och JSON. De big_data listan innehåller 5000 komplexa objekt.

big_data = [dict (a = enkel, när = datetime.now (). ersätt (mikrosekund = 0)) för jag i intervallet (5000)]

Ättikslag

Jag använder IPython här för dess praktiska % timeit magisk funktion som mäter körtider.

importera cPickle som pickle I [190]:% timeit serialized = pickle.dumps (big_data) 10 slingor, bäst av 3: 51 ms per slinga I [191]:% timeit deserialized = pickle.loads (serialiserade) 10 slingor, bäst av 3: 24,2 ms per slinga [192]: deserialiserad == big_data Ut [192]: Sann i [193]: len (serialiserad) Ut [193]: 747328

Standardindelningen tar 83,1 millisekunder att serialisera och 29,2 millisekunder för att deserialisera och den serialiserade storleken är 747.328 byte.

Låt oss försöka med det högsta protokollet.

I [195]:% timeit serialized = pickle.dumps (big_data, protocol = pickle.HIGHEST_PROTOCOL) 10 slingor, bäst av 3: 21,2 ms per slinga I [196]:% timeit deserialized = pickle.loads (serialiserade) 10 slingor, bäst av 3: 25,2 ms per slinga [197]: len (serialiserad) Ut [197]: 394350

Intressanta resultat. Serialiseringstiden minskade till endast 21,2 millisekunder, men deserialiseringstiden ökade lite till 25,2 millisekunder. Den serialiserade storleken krympte signifikant till 394 350 byte (52%).

JSON

I [253]% timeit serialized = json.dumps (big_data, cls = CustomEncoder) 10 slingor, bäst av 3: 34,7 ms per slinga [253]% timeit deserialized = json.loads (serialized, object_hook = decode_object) 10 slingor, bäst av 3: 148 ms per slinga In [255]: len (serialiserad) Ut [255]: 730000

Ok. Prestanda verkar vara lite sämre än Pickle för kodning, men mycket, mycket värre för avkodning: 6 gånger långsammare. Vad pågår? Detta är en artefakt av object_hook funktion som behöver springa för varje ordlista för att kontrollera om den behöver konvertera den till ett objekt. Att köra utan föremålskroken är mycket snabbare.

% timeit deserialized = json.loads (serialized) 10 slingor, bäst av 3: 36,2 ms per slinga

Läran här är att när serialisering och deserialisering till JSON, överväga mycket noggrant några anpassade kodningar eftersom de kan ha stor inverkan på den övergripande prestandan.

YAML

I [293]:% timeit serialized = yaml.dump (big_data) 1 slingor, bästa av 3: 1,22 s per slinga I [294]:% timeit deserialized = yaml.load (serialized) 1 slingor, bästa av 3: 2.03 s per slinga [295]: len (serialiserad) Ut [295]: 200091

Ok. YAML är verkligen, verkligen långsam. Men notera något intressant: den serialiserade storleken är bara 200,091 byte. Mycket bättre än både Pickle och JSON. Låt oss se inuti riktigt snabbt:

I [300]: skriv serialiserad [: 211] - a: & id001 booleansk: sant int_list: [1, 2, 3] ingen: null nummer: 3,44 text: sträng 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 är väldigt smart här. Det identifierade att alla 5.000 dikter delar samma värde för "a" -nyckeln, så det lagrar den bara en gång och refererar till den med hjälp av * id001 för alla objekt.

säkerhet

Säkerhet är ofta en kritisk fråga. Pickle och YAML, i kraft av att konstruera Python-objekt, är sårbara för körningsattacker. En smartformaterad fil kan innehålla godtycklig kod som kommer att utföras av Pickle eller YAML. Det finns inget behov av att vara orolig. Detta är av design och dokumenteras i Pickles dokumentation:

Varning: Pickle-modulen är inte avsedd att vara säker mot felaktigt eller skadligt konstruerade data. Unpickle aldrig data som tagits emot från en otillförlitlig eller obehörig källa.

Såsom i YAMLs dokumentation:

Varning: Det är inte säkert att ringa yaml.load med data som tagits emot från en otillförlitlig källa! yaml.load är lika kraftfull som pickle.load och det kan också kalla någon Python-funktion.

Du behöver bara förstå att du inte ska ladda serialiserad data mottagen från otillförlitliga källor med Pickle eller YAML. JSON är OK, men igen om du har egna kodare / avkodare än du kan bli utsatt för.

Yaml-modulen ger yaml.safe_load () funktion som laddar bara enkla objekt, men då förlorar du mycket YAMLs ström och kanske väljer att bara använda JSON.

Andra format

Det finns många andra serialiseringsformat tillgängliga. Här är några av dem.

protobuf

Protobuf- eller protokollbuffertar är Googles datautbytesformat. Det implementeras i C ++ men har Python-bindningar. Den har ett sofistikerat schema och packar data effektivt. Mycket kraftfull, men inte så lätt att använda.

MessagePack

MessagePack är ett annat populärt serialiseringsformat. Det är också binärt och effektivt, men i motsats till Protobuf behöver det inte ett schema. Den har ett typsystem som liknar JSON, men lite rikare. Nycklar kan vara vilken typ som helst, och inte bara strängar och strängar utan UTF8 stöds.

CBOR

CBOR står för kortfattad objektrepresentation. Återigen stöder den JSON datamodellen. CBOR är inte så känd som Protobuf eller MessagePack men är intressant av två skäl: 

  1. Det är en officiell Internetstandard: RFC 7049.
  2. Den var utformad speciellt för saker av Internet (IoT).

Hur man väljer?

Det här är den stora frågan. Med så många alternativ, hur väljer du? Låt oss ta hänsyn till de olika faktorer som bör beaktas:

  1. Skulle det serialiserade formatet vara mänskligt läsbart och / eller humanredigerbart?
  2. Kommer serialiserat innehåll att tas emot från otillförlitliga källor?
  3. Är serialisering / deserialisering en resultatflaskhals?
  4. Serierad data måste bytas ut med icke-Python-miljöer?

Jag gör det väldigt enkelt för dig och täcker flera vanliga scenarier och vilket format jag rekommenderar för var och en:

Auto-spara lokal status för ett pythonprogram

Använd pickle (cPickle) här med HIGHEST_PROTOCOL. Det är snabbt, effektivt och kan lagra och ladda de flesta Python-objekt utan någon särskild kod. Det kan också användas som en lokal ihållande cache.

Konfigurationsfiler

Definitivt YAML. Ingenting slår sin enkelhet för allt människor behöver läsa eller redigera. Det används framgångsrikt av Ansible och många andra projekt. I vissa situationer kanske du föredrar att använda raka Python-moduler som konfigurationsfiler. Det här kan vara rätt val, men då är det inte serialisering, och det är verkligen en del av programmet och inte en separat konfigurationsfil.

Web APIs

JSON är den tydliga vinnaren här. Dessa dagar brukar Web APIs oftast användas av JavaScript-webbprogram som talar JSON inhemskt. Vissa webb-API-er kan returnera andra format (t ex csv för täta tabulära resultatuppsättningar), men jag skulle hävda att du kan paketera csv-data till JSON med minimal overhead (det behöver inte repeteras varje rad som ett objekt med alla kolumnnamn). 

Högvolym / Låglängd Storskalig kommunikation

Använd ett av de binära protokollen: Protobuf (om du behöver ett schema), MessagePack eller CBOR. Kör dina egna tester för att verifiera prestanda och representativ kraft för varje alternativ.

Slutsats

Serialisering och deserialisering av Python-objekt är en viktig aspekt av distribuerade system. Du kan inte skicka Python-objekt direkt över ledningen. Du måste ofta samverka med andra system som implementeras på andra språk, och ibland vill du bara lagra ditt programs tillstånd i bestående lagring. 

Python levereras med flera serialiseringssystem i sitt standardbibliotek, och många fler är tillgängliga som moduler från tredje part. Att vara medveten om alla alternativ och fördelar och nackdelar hos var och en kommer att låta dig välja den bästa metoden för din situation.