Využití serializačního formátu MessagePack v Pythonu

12. 12. 2024
Doba čtení: 35 minut

Sdílet

Autor: Root.cz s využitím DALL-E
Se serializačním formátem nazvaným MessagePack jsme se již na stránkách Roota jednou setkali. Dnes si ukážeme, jak se tento formát používá v jazyku Python, a to včetně serializace N-dimenzionálních polí knihovny NumPy.

Obsah

1. Využití serializačního formátu MessagePack v Pythonu

2. Formát MessagePack

3. Některá omezení formátu MessagePack

4. Instalace balíčku msgpack i balíčku pro serializaci N-dimenzionálních polí

5. Prohlížení binárních souborů se serializovanými daty

6. Serializace primitivních datových typů do formátu MessagePack

7. Hodnota nil, resp. None

8. Serializace pravdivostních hodnot TrueFalse

9. Serializace celočíselných hodnot

10. Celočíselné hodnoty překračující 64bitový rozsah

11. Serializace hodnot s plovoucí řádovou čárkou

12. Složené datové typy ve formátu MessagePack

13. Krátké a dlouhé řetězce

14. Malá a rozsáhlá pole

15. Serializace map

16. Serializace N-dimenzionálních polí

17. Serializace 1D polí z balíčku NumPy

18. Příloha: alternativní binární formáty pro serializaci dat

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Využití serializačního formátu MessagePack v Pythonu

Se serializačním formátem nazvaným MessagePack jsme se již na stránkách Roota jednou setkali, a to konkrétně v souvislosti s programovacím jazykem Go. Připomeňme si ve stručnosti, že se v současnosti jedná o jeden z relativně velkého množství dostupných a používaných datových formátů určených pro serializaci a deserializaci dat různých typů s jejich případným přenosem do jiné aplikace či služby (poté se spíše setkáme s termíny marshalling a unmarshalling). Přenosem se přitom v tomto kontextu myslí jak lokální komunikace, tak i přenos do služby běžící na jiném počítači.

Již dříve jsme se ve stručnosti seznámili s využitím známého formátu JSON (ten se používá a někdy i zneužívá na mnoha místech) a nepřímo taktéž s formátem TOML používaným typicky pro konfigurační soubory (a mnohem méně často pro rozsáhlejší data). V případě JSONu se jedná o poměrně důležitý formát, protože JSON (a samozřejmě též XML) se v současnosti používá v mnoha webových službách a i když stále vznikají a jsou postupně adaptovány další formáty, ať již textové (YAML, edn) či binární (BSON, B-JSON, Smile, Protocol-Buffers), CBOR atd., je velmi pravděpodobné, že se JSON bude i nadále poměrně masivně využívat (a navíc pro něj existují užitečné nástroje typu jq). Nicméně pochopitelně existují situace, v nichž je vhodné textový a relativně neúsporný JSON nahradit právě nějakým binárním formátem.

I přesto, že se s výše uvedenými formáty JSON a XML setkáme prakticky ve všech oblastech moderního IT, nemusí se vždy jednat o to nejlepší možné řešení problému přenosu strukturovaných dat. Tyto formáty totiž data neukládají v kompaktní binární podobě a navíc je parsing numerických hodnot relativně zdlouhavý, což se projevuje zejména tehdy, pokud je nutné zpracovat skutečně obrovské množství dat (buď se tedy jedná o situaci, kdy je nutné zpracovat mnoho malých zpráv či událostí, nebo naopak rozsáhlé datové soubory). A právě v těchto situacích může být výhodnější sáhnout po nějakém vhodně navrženém binárním formátu, ideálně takovém formátu, který je popsán ve standardu a který je implementován pro více jazyků či ekosystémů. Takových úsporných formátů již dnes existuje velké množství, od staršího a dosti těžkopádného ASN.1 (Abstract Syntax Notation One) po formáty, které se snaží napodobit některé vlastnosti JSONu. Příkladem z této oblasti může být formát CBOR, jenž je mj. podporován knihovnou https://github.com/fxamacker/cbor, popř. formát BSON. A konečně, ve se především ve světě Go setkáme i s formátem nazvaným gob neboli Go Objects.

Jednou ze známých a relativně často nasazovaných „binárních alternativ“ k formátu JSON je i formát nazvaný MessagePack, s jehož základními vlastnostmi se seznámíme v navazujících kapitolách. Zaměříme se přitom na jeho použití v Pythonu, což s sebou nese určité specifické vlastnosti, které vyplývají z typového systému Pythonu (konkrétně z toho, že existuje celočíselný typ s neomezeným rozsahem či jediný numerický formát s plovoucí řádovou čárkou).

2. Formát MessagePack

Datový formát MessagePack je navržen takovým způsobem, aby byl „binárním protějškem“ známého a velmi často využívaného formátu JSON, ovšem s několika vylepšeními. Binární formát MessagePack umožňuje serializovat (ukládat) následující datové typy a pochopitelně i jejich kombinace, protože je vhodné si uvědomit, že mnohé datové typy jsou vlastně kontejnery pro hodnoty dalších typů (v MessagePacku se takto používají pole a taktéž mapy):

  1. Hodnotu nil odpovídající v JSONu hodnotě null. Tato hodnota je uložena v jediném bajtu, včetně typové informace.
  2. Pravdivostní hodnoty true a false. Opět je použit úsporný způsob uložení v jediném bajtu.
  3. Celá čísla (integer) s různou binární délkou. Malé hodnoty, s nimiž se setkáme nejčastěji, jsou uloženy v optimalizované (kratší) podobě. Jedná se o rozšíření oproti formátu JSON, který celá čísla nepodporuje.
  4. Čísla s plovoucí řádovou čárkou v jednoduché i dvojité přesnosti, a to (to je pro mnoho aplikací důležité) včetně všech speciálních hodnot, tedy kladného a záporného nekonečna i hodnoty „Not a Number“.
  5. Unicode řetězce, přičemž krátké řetězce jsou opět uloženy optimalizovaně.
  6. Sekvence bajtů, což je typ, který nám umožňuje například serializaci obrázků atd.
  7. Pole, jejichž prvky jsou prakticky jakéhokoli typu.
  8. Mapy, jejichž klíče i prvky jsou prakticky jakéhokoli typu (v tomto případě se jedná o rozšíření JSONu).
  9. Časová razítka. To je v praxi poměrně užitečný formát. JSON tuto možnost postrádá a proto se setkáme s mnoha různými způsoby uložení časových razítek (mnohé jsou navrženy špatně).
  10. Rozšíření (dvojice s typovou informací a hodnotou). Takto lze MessagePack rozšířit například o komplexní čísla, vektory atd. Podpora však musí existovat na obou komunikujících stranách!

Důležité přitom je, že způsob uložení dat určuje nejenom jejich hodnotu, ale i typ, takže přijímající strana získá například informaci „toto je hodnota False typu boolean“ nebo „toto je celé číslo s hodnotou 42“. Naproti tomu však nezískáme jméno příslušného atributu, takže obě komunikující strany musí mít (shodnou) informaci o tom, jaké datové struktury se přenáší a/nebo serializují, nebo je nutné použít mapu (což je nejčastější způsob).

Již z prvního bajtu každé serializované hodnoty lze určit její typ. Jak je to zařízeno je patrné z následující tabulky. Povšimněte si, že u mnoha typů je v prvním bajtu typ uložen jen v několika bitech a další bity tak lze využit i pro (částečné) uložení hodnoty:

Datový typ hodnota prvního bajtu (bin) hodnota prvního bajtu (hex)
positive fixint 0×xxxxxx 0×00 – 0×7f
fixmap 1000×xxx 0×80 – 0×8f
fixarray 1001×xxx 0×90 – 0×9f
fixstr 101×xxxx 0×a0 – 0×bf
nil (null, None) 11000000 0×c0
(není použit) 11000001 0×c1
false 11000010 0×c2
true 11000011 0×c3
bin 8 11000100 0×c4
bin 16 11000101 0×c5
bin 32 11000110 0×c6
ext 8 11000111 0×c7
ext 16 11001000 0×c8
ext 32 11001001 0×c9
float 32 11001010 0×ca
float 64 11001011 0×cb
uint 8 11001100 0×cc
uint 16 11001101 0×cd
uint 32 11001110 0×ce
uint 64 11001111 0×cf
int 8 11010000 0×d0
int 16 11010001 0×d1
int 32 11010010 0×d2
int 64 11010011 0×d3
fixext 1 11010100 0×d4
fixext 2 11010101 0×d5
fixext 4 11010110 0×d6
fixext 8 11010111 0×d7
fixext 16 11011000 0×d8
str 8 11011001 0×d9
str 16 11011010 0×da
str 32 11011011 0×db
array 16 11011100 0×dc
array 32 11011101 0×dd
map 16 11011110 0×de
map 32 11011111 0×df
negative fixint 111×xxxx 0×e0 – 0×ff

3. Některá omezení formátu MessagePack

Možnosti binárního formátu MessagePack, kterým se v dnešním článku zabýváme, skutečně do značné míry odpovídají možnostem JSONu s několika rozšířeními, o nichž jsme se zmínili výše a s nimiž se ještě setkáme v demonstračních příkladech v praktické části článku. Ovšem musíme se zmínit i o některých principiálních omezeních, z nichž některé jsou společné i dalším často používaným serializačním formátům (nehledě na to, zda jsou textové či binární):

  1. celá čísla mohou nabývat hodnoty z rozsahu –263 až 264-1 (to není chyba – pro kladné hodnoty totiž v MessagePacku existuje typ bez znaménka, tedy unsigned int). Rozsah je zde tedy mnohem větší, než v případě JSONu, kde je nutné pracovat s typem double.
  2. maximální délka řetězců je rovna 4GB (což v praxi pravděpodobně nebude příliš velké omezení)
  3. maximální délka binárního bloku je taktéž rovna 4GB (což již v praxi někdy může vadit)
  4. maximální počet prvků v poli je roven 232-1
  5. maximální počet dvojic klíč-hodnota v mapě je taktéž roven 232-1
  6. nelze ukládat ukazatele a tím pádem ani přímo pracovat se stromy, obecnými grafy atd. Tento nedostatek se částečně dá nahradit mapami obsahujícími další hodnoty (pole, mapy). Totéž se týká JSONu a mnoha dalších přenositelných formátů.
  7. co je ze sémantického hlediska poněkud problematické – není podporován typ „množina“ (na to jsme si museli zvyknout i u JSONu).
  8. taktéž není podporován typ decimal, konkrétně numerické hodnoty s plovoucí desetinnou řádovou čárkou.
Poznámka: podrobnosti o tom, jak jsou ukládány jednotlivé typy hodnot, si ukážeme ve druhé (prakticky zaměřené) části dnešního článku.

4. Instalace balíčku msgpack> i balíčku pro serializaci N-dimenzionálních polí

Pro práci s formátem MessagePack v Pythonu je určen balíček nazvaný msgpack. Ten již bývá nainstalován společně s Pythonem (protože ho využívají některé nástroje nad Pythonem postavené), ovšem pokud tomu tak není, je instalace balíčku msgpack snadná a přímočará:

$ pip install --user msgpack

Pro jednoduchý test, zda instalace balíčku proběhla v pořádku, použijeme tento skript:

import msgpack
 
help(msgpack)

Měla by se zobrazit nápověda:

Help on package msgpack:
 
NAME
    msgpack
 
PACKAGE CONTENTS
    _cmsgpack
    exceptions
    ext
    fallback
 
FUNCTIONS
    dump = pack(o, stream, **kwargs)
        Pack object `o` and write it to `stream`
 
        See :class:`Packer` for options.
 
    dumps = packb(o, **kwargs)
        Pack object `o` and return packed bytes
 
        See :class:`Packer` for options.
 
    load = unpack(stream, **kwargs)
        Unpack an object from `stream`.
 
        Raises `ExtraData` when `stream` contains extra bytes.
        See :class:`Unpacker` for options.
 
    pack(o, stream, **kwargs)
        Pack object `o` and write it to `stream`
 
        See :class:`Packer` for options.
 
    packb(o, **kwargs)
        Pack object `o` and return packed bytes
 
        See :class:`Packer` for options.
 
    unpack(stream, **kwargs)
        Unpack an object from `stream`.
 
        Raises `ExtraData` when `stream` contains extra bytes.
        See :class:`Unpacker` for options.
 
DATA
    version = (1, 0, 6)
 
VERSION
    1.0.6
 
FILE
    /usr/lib64/python3.12/site-packages/msgpack/__init__.py

Povšimněte si, že k dispozici jen jen několik funkcí; samotné API je pojato minimalisticky (což je jen dobře):

Funkce Stručný popis
pack serializace hodnoty do otevřeného streamu
packb serializace hodnoty to sekvence bajtů
unpack deserializace hodnoty z otevřeného streamu
unpackb deserializace hodnoty ze sekvence bajtů
   
dump provádí tutéž operaci co pack()
dumps provádí tutéž operaci co packb()
load provádí tutéž operaci co unpack()
loads provádí tutéž operaci co unpackb()

Dále si nainstalujeme přídavný balíček, který nám umožní serializaci a deserializaci N-dimenzionálních polí knihovny Numpy. Jedná se o velmi užitečný doplněk:

$ pip install --user msgpack-numpy

Tento balíček již typicky není v systému dostupný, takže se skutečně nainstaluje:

Collecting msgpack-numpy
  Downloading msgpack_numpy-0.4.8-py2.py3-none-any.whl.metadata (5.0 kB)
Collecting numpy>=1.9.0 (from msgpack-numpy)
  Downloading numpy-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (62 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 62.0/62.0 kB 955.9 kB/s eta 0:00:00
Requirement already satisfied: msgpack>=0.5.2 in /usr/lib64/python3.12/site-packages (from msgpack-numpy) (1.0.6)
Downloading msgpack_numpy-0.4.8-py2.py3-none-any.whl (6.9 kB)
Downloading numpy-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (16.1 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16.1/16.1 MB 8.0 MB/s eta 0:00:00
Installing collected packages: numpy, msgpack-numpy
Successfully installed msgpack-numpy-0.4.8 numpy-2.2.0

5. Prohlížení binárních souborů se serializovanými daty

Pro prohlížení obsahu vytvořených binárních souborů se serializovanými daty je možné použít například nějakou formu hexadecimálního prohlížeče. Hexadecimálních prohlížečů a editorů existuje (pro Linux) relativně velké množství. První dva nástroje nazvané od a hexdump (zkráceně hd) pracují jako relativně jednoduché jednosměrné filtry (navíc bývají nainstalovány společně se základní sadou nástrojů), ovšem další nástroj pojmenovaný xxd již může být použit pro obousměrný převod (filtraci), tj. jak pro transformaci původního binárního souboru do čitelného tvaru (většinou s využitím šestnáctkové soustavy), tak i pro zpětný převod. Díky tomu je možné nástroj xxd použít například ve funkci pluginu do běžných textových editorů (v textovém editoru se v takovém případě edituje hexadecimální výpis, při uložení se data konvertují zpět do původní úsporné binární podoby).

Další nástroj pojmenovaný hexdiff dokáže porovnat obsah dvou binárních souborů a poslední zmíněný nástroj mcview je, na rozdíl od předchozí čtveřice, aplikací s interaktivním ovládáním a plnohodnotným textovým uživatelským prostředím (patří do sady nástrojů vytvořených okolo Midnight Commanderu a instaluje se společně s tímto nástrojem).

Poznámka: v kontextu dnešního článku si vystačíme s možnostmi nabízenými nástrojem od neboli octal dump. Jméno tohoto nástroje je ve skutečnosti zavádějící, protože dokáže zobrazit obsah binárního soubory mnoha různými způsoby, nejenom v osmičkové soustavě. Již fakt, že jméno této utility má pouhá dvě písmena, napovídá, že se jedná o nástroj pocházející již z prvních verzí Unixu. Původní varianty utility od vypisovaly obsah zvoleného souboru (alternativně standardního vstupu či zvoleného zařízení) s využitím osmičkové soustavy, ovšem GNU verze od nabízí uživatelům mnohem víc možností, a to včetně použití hexadecimální soustavy (ostatně i proto o této utilitě dnes píšeme), zformátování sousedních čtyř bajtů do čísla typu single/float, dtto pro osm bajtů a čísla typu double apod.

6. Serializace primitivních datových typů do formátu MessagePack

Již v úvodních dvou kapitolách jsme si řekli, že do formátu MessagePack je možné ukládat jak hodnoty jednoduchých (primitivních) datových typů, tak i hodnoty složených datových typů (což jsou různé typy kontejnerů – pole a mapy). Začneme primitivními datovými typy, protože binární formát MessagePack je navržen takovým způsobem, aby byl způsob jejich uložení v co největší míře efektivní – a to nejenom co se týká celkového objemu dat, ale i jednoduchosti nebo naopak složitosti zakódování a dekódování hodnot. Nativně jsou podporovány následující primitivní datové typy:

  1. Typ none s jedinou hodnotou nil (v Pythonu odpovídá typu NoneType)
  2. Typ boolean s hodnotami true a false (v Pythonu odpovídá typu bool)
  3. Typ unsigned integer s plně 64 bitovým rozsahem (ve standardním Pythonu nemá přímý protějšek)
  4. Typ signed integer s plně 64 bitovým rozsahem (částečně odpovídá typu int)
  5. Typ float/single/float32 s plovoucí řádovou čárkou (ve standardním Pythonu nemá přímý protějšek)
  6. Typ double/float64 s plovoucí řádovou čárkou (v Pythonu odpovídá typu float – pozor na odlišné pojmenování!)

7. Hodnota nil, resp. None

Začneme tím zdánlivě nejjednodušším možným příkladem, který je však v praxi poněkud problematický při předávání serializovaných hodnot mezi různými programovacími jazyky a proto je nutné se o něm zmínit. Konkrétně se budeme zabývat způsobem serializace Pythonovské hodnoty None. Ta je použita stejným způsobem jako hodnota null v JSONu, tedy pro indikaci chybějících dat. Přitom None má v Pythonu svůj vlastní datový typ pojmenovaný taktéž None, zatímco například nil v jazyce Go nemusí být nutně přiřazeno k datovému typu (z tohoto pohledu je v čase překladu beztypové, viz celý článek, který jsme na toto téma vydali: na rozdíl od samotného jazyka Go).

Zdrojový kód demonstračního příkladu, který serializuje hodnotu None, se skládá z několika operací:

  1. Vytvoření a otevření nového (binárního) souboru pro zápis
  2. Konstrukce objektu/struktury použité pro serializaci
  3. Vlastní serializace dat přímo do otevřeného binárního souboru

V Pythonu 3.x může takový program vypadat následovně:

import msgpack
 
value = None
 
with open("nil.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Pro zajímavost se podívejme, jak by podobný program vypadal při realizaci v programovacím jazyce Go, v němž se explicitně kontrolují případné chybové stavy atd.:

package main
 
import (
        "log"
        "os"
 
        "github.com/ugorji/go/codec"
)
 
const filename = "/tmp/nil.bin"
 
func main() {
        // vytvořit soubor s binárními daty
        fout, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
        if err != nil {
                log.Fatal(err)
        }
        defer fout.Close()
 
        log.Print("Output file created")
 
        // handler
        var handler codec.MsgpackHandle
 
        // objekt realizující zakódování dat
        encoder := codec.NewEncoder(fout, &handler)
 
        log.Print("Encoder created")
 
        // zakódování dat
        err = encoder.Encode(nil)
        if err != nil {
                log.Fatal(err)
        }
 
        log.Print("Done")
}

V obou případech, tedy nezávisle na použitém programovacím jazyku, by však měl mít výsledný soubor nil.bin naprosto totožný obsah. Výsledkem serializace totiž bude binární soubor obsahující jediný bajt s hodnotou 0×c0, o čemž se ostatně můžeme velmi snadno přesvědčit:

$ od -A x -t x1 -v nil.bin
 
000000 c0
000001

Obsah tohoto souboru pochopitelně plně odpovídá specifikaci.

Poznámka: je důležité si uvědomit, že tato hodnota má význam „chybějící údaj“ nebo „neexistující údaj“ a nejedná se v žádném případě o ukazatel (v Go a vlastně i Pythonu se tyto dvě rozdílné sémantické významy poněkud překrývají).

8. Serializace pravdivostních hodnot TrueFalse

V serializačním formátu MessagePack jsou plně podporovány i pravdivostní hodnoty True a False, což v praxi znamená, že není nutné (a ani ze sémantického pohledu rozumné) používat pro reprezentaci pravdivostních hodnot například hodnoty 0 a 1 či 0 a –1. Navíc jsou pravdivostní hodnoty uloženy relativně rozumným způsobem – v jediném bajtu (včetně informace o typu). O tom se budeme moci velmi snadno přesvědčit na dvojici příkladů, které uloží hodnotu True a ve druhém případě hodnotu False:

import msgpack
 
value = True
 
with open("true.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)
import msgpack
 
value = False
 
with open("false.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Nyní se podívejme na to, jak jsou tyto dvě hodnoty uloženy do výsledného souboru:

$ od -A x -t x1 -v true.bin
 
000000 c3
000001
 
 
 
$ od -A x -t x1 -v false.bin
 
000000 c2
000001

Což opět plně odpovídá specifikaci.

9. Serializace celočíselných hodnot

V této kapitole si ukážeme způsob serializace celočíselných hodnot, což je již z pohledu programátora mnohem zajímavější problém. V tomto ohledu totiž museli tvůrci formátu MessagePack splnit dva protichůdné požadavky:

  1. reprezentovat co největší rozsah hodnot, ideálně 64bitové hodnoty
  2. na druhou stranu je použití 64bitů (8 bajtů) ve všech případech až trestuhodné plýtvání místem (a to mnohdy i oproti textovému JSONu v případě ukládání hodnot okolo nuly)

Výsledkem snahy o splnění obou požadavků je flexibilní způsob uložení celých čísel v jednom, dvou, třech, pěti či devíti bajtech – vždy v závislosti na konkrétní hodnotě a taktéž na tom, zda se jedná o hodnotu kladnou či zápornou. Specifikace uložení celých čísel ve skutečnosti není příliš složitá a můžeme si ji snadno otestovat na několika demonstračních příkladech.

Poznámka: povšimněte si striktního použití pořadí bajtů big endian!

Nejprve si otestujeme uložení velmi malého celého čísla v rozsahu 0 až 127:

import msgpack
 
value = 42
 
with open("int_tiny.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Výsledkem je v tomto případě pouhý jeden bajt, který obsahuje jak informace o datovém typu, tak i vlastní hodnotu:

$ od -A x -t x1 -v int_tiny.bin
 
000000 2a
000001

Uložení čísla většího než 127, ale menšího než 256:

import msgpack
 
value = 200
 
with open("int_small.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Výsledný soubor bude mít velikost dva bajty, opět přesně podle specifikace (první bajt obsahuje typ, druhý hodnotu):

$ od -A x -t x1 -v int_small.bin
 
000000 cc c8
000002

Celé číslo větší než 255, ale menší než 216 se uloží do tří bajtů:

import msgpack
 
value = 1000
 
with open("int_large.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Výsledek:

$ od -A x -t x1 -v int_large.bin
 
000000 cd 03 e8
000003

kde 0×3e8 skutečně v desítkové soustavě odpovídá hodnotě 1000.

Serializace čísla většího než 216:

import msgpack
 
value = 100000
 
with open("int_larger.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Opět je použit jeden bajt se specifikací typu, za kterým následuje čtveřice bajtů 0×0186a0 = 100000:

$ od -A x -t x1 -v int_larger.bin
 
000000 ce 00 01 86 a0
000005

A konečně hodnota 260:

import msgpack
 
value = 2**60
 
with open("int_long.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Jedná se o 64bitovou hodnotu uloženou v devíti bajtech:

$ od -A x -t x1 -v long_int.bin
 
000000 cf 10 00 00 00 00 00 00 00
000009

Podobné příklady lze vytvořit i pro záporná čísla, například:

import msgpack
 
value = -10
 
with open("negative_int.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

S výsledkem:

$ od -A x -t x1 -v negative_int.bin
 
000000 f6
000001

10. Celočíselné hodnoty překračující 64bitový rozsah

Celočíselné hodnoty, které přesahují 64bitový rozsah, nejsou v současné verzi MessagePacku podporovány, tedy za předpokladu, že si nenapíšete rozšíření pro vlastní datový typ (více o tomto tématu si řekneme dále). Zajímavé bude zjistit, co se tedy stane v případě, že budeme serializovat větší celočíselnou hodnotu. Otestování je snadné:

import msgpack
 
value = 2**64
 
with open("too_long_int.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Výsledek nebude v tomto případě příliš potěšující – dojde k vyhození běhové výjimky typu OverflowError:

Traceback (most recent call last):
  File "msgpack/_packer.pyx", line 177, in msgpack._cmsgpack.Packer._pack
OverflowError: Python int too large to convert to C unsigned long
 
During handling of the above exception, another exception occurred:
 
Traceback (most recent call last):
  File "/home/ptisnovs/src/most-popular-python-libs/msgpack/serialize_too_long_int.py", line 6, in
    packed = msgpack.packb(value)
             ^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/site-packages/msgpack/__init__.py", line 36, in packb
    return Packer(**kwargs).pack(o)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "msgpack/_packer.pyx", line 294, in msgpack._cmsgpack.Packer.pack
  File "msgpack/_packer.pyx", line 300, in msgpack._cmsgpack.Packer.pack
  File "msgpack/_packer.pyx", line 297, in msgpack._cmsgpack.Packer.pack
  File "msgpack/_packer.pyx", line 188, in msgpack._cmsgpack.Packer._pack
OverflowError: Integer value out of range

11. Serializace hodnot s plovoucí řádovou čárkou

Ve formátu MessagePack jsou podle očekávání podporovány i hodnoty s plovoucí řádovou čárkou. Jedná se jak o hodnoty s jednoduchou přesností (single, float, float32), tak i o hodnoty s dvojitou přesností (double, float64). V Pythonu existuje jediný datový typ pro reprezentaci hodnoty s plovoucí řádovou čárkou, a ten odpovídá typu double/float64, takže se (při serializaci) většinou s hodnotou typu single nesetkáme:

import msgpack
 
value = 3.14
 
with open("float.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Prozkoumejme výsledný soubor, který by měl mít délku devíti bajtů:

000000 cb 40 09 1e b8 51 eb 85 1f
000009
Poznámka: výsledek si můžete ověřit na této stránce po zadání hodnoty 3.14 a přečtení 64bitového výsledku v hexadecimálním tvaru.

V mnoha serializačních formátech, zejména těch textově orientovaných (JSON, XML) se mnohdy poněkud zapomíná na hodnoty NaN a taktéž na kladné i záporné nekonečno. V případě MessagePacku jsou tyto speciální hodnoty plně podporovány, což je v poměrně mnoha oblastech důležité (samozřejmě otázkou je, jak tyto hodnoty vznikly, to však nemá řešit přenosový protokol).

Otestujme si nejdříve serializaci hodnoty NaN, tj. například na výsledek 0/0, sin(∞), √-1 atd.:

import math
import msgpack
 
value = math.nan
 
with open("nan.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Výsledný soubor obsahuje devět bajtů – specifikaci formátu následovanou hodnotou NaN odpovídající IEEE 754, což si opět můžete ověřit na stránce https://baseconvert.com/ieee-754-floating-point po zadání „nan“ do vstupního políčka:

000000 cb 7f f8 00 00 00 00 00 00
000009

Podobně si můžeme ověřit, jak je uloženo kladné nebo záporné nekonečno. Začneme kladným nekonečnem:

import math
import msgpack
 
value = math.inf
 
with open("inf.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Výsledek:

000000 cb 7f f0 00 00 00 00 00 00
000009

V případě záporného nekonečně se pouze změní jediný bit, takže druhý bajt bude namísto hodnoty 7f obsahovat hodnotu ff:

import math
import msgpack
 
value = -math.inf
 
with open("negative_inf.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Výsledných devět bajtů:

000000 cb ff f0 00 00 00 00 00 00
000009

12. Složené datové typy ve formátu MessagePack

Po popisu způsobu uložení jednoduchých datových typů (což vlastně ve skutečnosti nebylo nic složitého) si v dnešním článku ukážeme, jakým způsobem je ve formátu MessagePack realizováno uložení složených datových typů. Do této kategorie se řadí především řetězce, sekvence bajtů, pole, ale v neposlední řadě i velmi důležité mapy, které lze použít například pro uložení atributů objektů (ostatně naprosto stejně je tato problematika řešená v JSONu). Opět uvidíme, že u některých výše zmíněných datových typů je dbáno na efektivitu výsledného binárního souboru, a to jak z hlediska celkového objemu dat, tak i složitosti kódování a dekódování těchto dat.

13. Krátké a dlouhé řetězce

Takřka nepostradatelným složeným datovým typem jsou řetězce. Interně se pro jejich uložení používá UTF-8. Neméně důležitá je však informace o tom, jak dlouhý řetězec je. Délka řetězce je uložena před vlastní znaky a to konkrétně tak, že pro krátké řetězce je délka uložena přímo v bajtu se specifikací typu (tedy neztratíme ani jediný bajt!) a pro delší řetězce je délka uložena v jednom, dvou či v celých čtyřech bajtech.

Velmi krátký řetězec, menší než 31 bajtů (nikoli znaků!), se serializuje takto:

import msgpack
 
value = "Hello"
 
with open("short_string.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

V tomto případě je délka řetězce uložena v prvním bajtu, přičemž první tři bity tohoto bajtu určují datový typ:

$ od -A x -t x1z -v short_string.bin
 
000000 a5 48 65 6c 6c 6f                                <.Hello>
000006

Vyzkoušejme si nyní poněkud delší řetězec:

import msgpack
 
value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
 
with open("longer_string.bin", "wb") as outfile:
     packed = msgpack.packb(value)
     outfile.write(packed)

Takový řetězec po serializaci obsahuje v první bajtu konstantu 0×d9 a poté délku řetězce v jediném bajtu. Následují kódy jednotlivých znaků:

000000 d9 ea 4c 6f 72 65 6d 20 69 70 73 75 6d 20 64 6f  >..Lorem ipsum do<
000010 6c 6f 72 20 73 69 74 20 61 6d 65 74 2c 20 63 6f  >lor sit amet, co<
000020 6e 73 65 63 74 65 74 75 72 20 61 64 69 70 69 73  >nsectetur adipis<
000030 63 69 6e 67 20 65 6c 69 74 2c 20 73 65 64 20 64  >cing elit, sed d<
000040 6f 20 65 69 75 73 6d 6f 64 20 74 65 6d 70 6f 72  >o eiusmod tempor<
000050 20 69 6e 63 69 64 69 64 75 6e 74 20 75 74 20 6c  > incididunt ut l<
000060 61 62 6f 72 65 20 65 74 20 64 6f 6c 6f 72 65 20  >abore et dolore <
000070 6d 61 67 6e 61 20 20 20 20 61 6c 69 71 75 61 2e  >magna    aliqua.<
000080 20 55 74 20 65 6e 69 6d 20 61 64 20 6d 69 6e 69  > Ut enim ad mini<
000090 6d 20 76 65 6e 69 61 6d 2c 20 71 75 69 73 20 6e  >m veniam, quis n<
0000a0 6f 73 74 72 75 64 20 65 78 65 72 63 69 74 61 74  >ostrud exercitat<
0000b0 69 6f 6e 20 75 6c 6c 61 6d 63 6f 20 6c 61 62 6f  >ion ullamco labo<
0000c0 72 69 73 20 6e 69 73 69 20 75 74 20 61 6c 69 71  >ris nisi ut aliq<
0000d0 75 69 70 20 65 78 20 65 61 20 63 6f 6d 6d 6f 64  >uip ex ea commod<
0000e0 6f 20 63 6f 6e 73 65 71 75 61 74 2e              >o consequat.<
0000ec

Řetězec delší než 255 znaků, ale kratší než 65536 znaků:

import msgpack
 
value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
 
with open("even_longer_string.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

V tomto případě je první bajt roven konstantě 0×da. Za ní následují dva bajty s délkou řetězce v bajtech, konkrétně celkem 0×1bd=445 bajtů. A poté již vlastní znaky tvořící řetězec:

000000 da 01 bd 4c 6f 72 65 6d 20 69 70 73 75 6d 20 64  >...Lorem ipsum d<
000010 6f 6c 6f 72 20 73 69 74 20 61 6d 65 74 2c 20 63  >olor sit amet, c<
000020 6f 6e 73 65 63 74 65 74 75 72 20 61 64 69 70 69  >onsectetur adipi<
000030 73 63 69 6e 67 20 65 6c 69 74 2c 20 73 65 64 20  >scing elit, sed <
000040 64 6f 20 65 69 75 73 6d 6f 64 20 74 65 6d 70 6f  >do eiusmod tempo<
000050 72 20 69 6e 63 69 64 69 64 75 6e 74 20 75 74 20  >r incididunt ut <
000060 6c 61 62 6f 72 65 20 65 74 20 64 6f 6c 6f 72 65  >labore et dolore<
000070 20 6d 61 67 6e 61 20 61 6c 69 71 75 61 2e 20 55  > magna aliqua. U<
000080 74 20 65 6e 69 6d 20 61 64 20 6d 69 6e 69 6d 20  >t enim ad minim <
000090 76 65 6e 69 61 6d 2c 20 71 75 69 73 20 6e 6f 73  >veniam, quis nos<
0000a0 74 72 75 64 20 65 78 65 72 63 69 74 61 74 69 6f  >trud exercitatio<
0000b0 6e 20 75 6c 6c 61 6d 63 6f 20 6c 61 62 6f 72 69  >n ullamco labori<
0000c0 73 20 6e 69 73 69 20 75 74 20 61 6c 69 71 75 69  >s nisi ut aliqui<
0000d0 70 20 65 78 20 65 61 20 63 6f 6d 6d 6f 64 6f 20  >p ex ea commodo <
0000e0 63 6f 6e 73 65 71 75 61 74 2e 20 44 75 69 73 20  >consequat. Duis <
0000f0 61 75 74 65 20 69 72 75 72 65 20 64 6f 6c 6f 72  >aute irure dolor<
000100 20 69 6e 20 72 65 70 72 65 68 65 6e 64 65 72 69  > in reprehenderi<
000110 74 20 69 6e 20 76 6f 6c 75 70 74 61 74 65 20 76  >t in voluptate v<
000120 65 6c 69 74 20 65 73 73 65 20 63 69 6c 6c 75 6d  >elit esse cillum<
000130 20 64 6f 6c 6f 72 65 20 65 75 20 66 75 67 69 61  > dolore eu fugia<
000140 74 20 6e 75 6c 6c 61 20 70 61 72 69 61 74 75 72  >t nulla pariatur<
000150 2e 20 45 78 63 65 70 74 65 75 72 20 73 69 6e 74  >. Excepteur sint<
000160 20 6f 63 63 61 65 63 61 74 20 63 75 70 69 64 61  > occaecat cupida<
000170 74 61 74 20 6e 6f 6e 20 70 72 6f 69 64 65 6e 74  >tat non proident<
000180 2c 20 73 75 6e 74 20 69 6e 20 63 75 6c 70 61 20  >, sunt in culpa <
000190 71 75 69 20 6f 66 66 69 63 69 61 20 64 65 73 65  >qui officia dese<
0001a0 72 75 6e 74 20 6d 6f 6c 6c 69 74 20 61 6e 69 6d  >runt mollit anim<
0001b0 20 69 64 20 65 73 74 20 6c 61 62 6f 72 75 6d 2e  > id est laborum.<
0001c0

A konečně řetězec delší než 65535 bajtů

import msgpack
 
value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
 
value *= 300
print(len(value))
 
with open("huge_string.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Na začátku souboru je uložen bajt 0×db a následuje čtyřbajtová délka řetězce. Poté již následují jednotlivé znaky řetězce:

000000 db 00 01 0e b4 4c 6f 72 65 6d 20 69 70 73 75 6d  >.....Lorem ipsum<
000010 20 64 6f 6c 6f 72 20 73 69 74 20 61 6d 65 74 2c  > dolor sit amet,<
000020 20 63 6f 6e 73 65 63 74 65 74 75 72 20 61 64 69  > consectetur adi<
000030 70 69 73 63 69 6e 67 20 65 6c 69 74 2c 20 73 65  >piscing elit, se<
000040 64 20 64 6f 20 65 69 75 73 6d 6f 64 20 74 65 6d  >d do eiusmod tem<

14. Malá a rozsáhlá pole

Pole, a to pole prvků libovolných typů, se do formátu MessagePack opět ukládá podle toho, kolik prvků takové pole obsahuje. Pole s prvky, jejichž počet nepřesáhne patnáct, obsahuje pouze jediný bajt navíc. Obsah tohoto bajtu určuje, že se jedná o pole a současně i ve spodních čtyřech bitech obsahuje počet prvků pole.

import msgpack
 
value = [1, 2, 3, 4]
 
with open("array.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Výše uvedené pole se čtyřmi prvky je uloženo v pouhých pěti bajtech a to z toho důvodu, že hodnoty prvků samy o sobě mají tak malou hodnotu, že každý z nich může být uložen v jediném bajtu. První bajt určuje jak typ (pole), tak i počet jeho prvků:

$ od -A x -t x1 -v short_array.bin
 
000000 94 01 02 03 04
000005

Naproti tomu druhé serializované pole již obsahuje prvky s relativně vyššími hodnotami:

import msgpack
 
value = [100, 200, 300, 400]
 
with open("array2.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Nyní bude soubor delší, protože již některé prvky nelze uložit do jediného bajtu:

$ od -A x -t x1 -v short_array2.bin
 
000000 94 64 d1 00 c8 d1 01 2c d1 01 90
00000b

V tomto případě první bajt obsahuje typ (pole) s jeho délkou. Následuje bajt s hodnotou 0×64=100, tedy první prvek (jediný bajt), další prvek je uložen ve třech bajtech (0×d1 = typ, 0×00c8=200 je hodnota) atd.

Poznámka: můžeme vidět, že každý prvek může být uložen různým způsobem a pole tedy nejsou heterogenní ani s ohledem na datový typ z pohledu programátora (ve skutečnosti se tedy spíše jedná o seznamy) ani z pohledu binárního formátu.

Pole delší než patnáct prvků se dále rozlišují podle toho, zda je celkový počet prvků menší než 216-1 nebo větší než tato hodnota. Podle počtu prvků se volí počet bajtů pro uložení délky pole – dva či čtyři bajty. My si dnes ukážeme pouze první typ, tj. pole menší než 216-1 prvků:

import msgpack
 
value = []
 
for i in range(1000):
    value.append(i)
 
with open("array3.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Nejprve je uveden typ (0×dc) a počet prvků 0×03e8=1000. Dále již následují hodnoty jednotlivých prvků. Pro prvních 128 prvků postačuje pro uložení použít jediný bajt:

000000 dc 03 e8 00 01 02 03 04 05 06 07 08 09 0a 0b 0c
000010 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c
000020 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c
000030 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c
000040 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c
000050 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c
000060 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c
000070 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c
000080 7d 7e 7f

Větší hodnoty jsou již uloženy složitějším způsobem, protože první bajt každé trojice obsahuje typ (0×d1):

000080          d1 00 80 d1 00 81 d1 00 82 d1 00 83 d1
000090 00 84 d1 00 85 d1 00 86 d1 00 87 d1 00 88 d1 00
0000a0 89 d1 00 8a d1 00 8b d1 00 8c d1 00 8d d1 00 8e
0000b0 d1 00 8f d1 00 90 d1 00 91 d1 00 92 d1 00 93 d1

15. Serializace map

Ve formátu JSON se prakticky vždy setkáme s mapami, resp. přesněji řečeno s asociativními poli. Tuto datovou strukturu lze použít i ve formátu MessagePack a to dokonce ještě ve vylepšené variantě, protože klíči mohou být hodnoty jakéhokoli typu, nejenom řetězce. Ukažme si ovšem základní použití s řetězci jako klíči:

import msgpack
 
value = {
        "foo": 42,
        "bar": None,
        "baz": 3.14,
        }
 
with open("dict.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Tato mapa se dvěma dvojicemi klíč+hodnota bude uložena v pouhých dvaceti čtyřech bajtech:

Mapa obsahuje tři dvojice, což je malý počet. Z tohoto důvodu je typ (mapa) i počet dvojic klíč-hodnota uložena v jediném bajtu 0×80+3=0×83. Následuje stejný obsah, jako v případě polí:

000000 83 a3 66 6f 6f 2a a3 62 61 72 c0 a3 62 61 7a cb  >..foo*.bar..baz.<
000010 40 09 1e b8 51 eb 85 1f                          >@...Q...<
000018
Bajty Význam
a3 66 6f 6f řetězec „foo“ o délce tří bajtů
2a malé celé číslo 42
a3 62 61 72 řetězec „bar“ o délce tří bajtů
c0 hodnota nil/None
a3 62 61 7a řetězec „baz“ o délce tří bajtů
cb 40 09 1e b8 51 eb 85 1f hodnota typu double = 3.14 (otestujte zde)

16. Serializace N-dimenzionálních polí

V mnoha oblastech souvisejících s IT se setkáme s daty, která jsou uložena v N-rozměrných polích (ND array). Nejčastěji se s velkou pravděpodobností setkáme s jednorozměrnými poli (neboli vektory), protože například zvukové záznamy jsou vlastně tvořeny sekvencí hodnot zvukových vzorků (samplů). A pochopitelně prakticky každý IT systém pracuje s obrazovými daty (ty si můžeme představit buď jako matice nebo jako trojrozměrná pole, v případě, že barvové roviny tvoří třetí dimenzi). Relativně často se setkáme i s vícerozměrnými poli, například v oblasti statistiky, lineární algebry, datové analýzy, strojového učení, zpracování medicínských či astronomických dat apod. Současně se jedná o datové struktury a operace, u nichž má velký smysl využít SIMD instrukce, které jsou dostupné na všech moderních mikroprocesorových architekturách. A právě z tohoto důvodu jsme se na stránkách Roota již mnohokrát setkali s programovacími jazyky, popř. s knihovnami, které jsou určeny právě pro zpracování n-rozměrných polí.

Víme již, že práce s N-rozměrnými poli je poměrně dobře podporována jak ve specializovaných programovacích jazycích (APL, J, K, …), tak i například v Pythonu, pro nějž byla vytvořena populární knihovna Numpy. Taktéž jsme se setkali s balíčky pro práci s N-rozměrnými poli určenými pro programovací jazyk Go. Připomeňme si, že se jednalo především o balíčky Gonum Numerical Packages a taktéž o balíček narray. Kvůli tomu, že se v oblasti statistiky, datové analýzy či strojového učení stále více používá programovací jazyk Python, je mnohdy nutné zajistit předávání dat (reprezentovaných ve formě N-rozměrných polí) právě mezi Pythonem a nástroji vytvořenými v dalších jazycích.

Taková data lze předávat v několika standardních formátech k tomu určených, ovšem využít lze i MessagePack. V tomto případě jsou data uložena jako „rozšiřující typ“ a pro jejich serializaci a deserializaci lze použít podpůrný balíček msgpack_numpy.

17. Serializace 1D polí z balíčku NumPy

Vyzkoušejme si nyní serializaci krátkého pole obsahujícího pouze tři prvky typu bajt. Povšimněte si, jak je kód balíčku msgpack modifikován přes funkci msgpack_numpy.patch(), což je mimochodem dosti nepěkný trik. Ovšem samotná serializace již probíhá standardním způsobem:

import msgpack
import msgpack_numpy as m
 
import numpy as np
 
m.patch()
 
value = np.array([1, 2, 3], dtype=np.byte)
 
with open("array_1d_byte.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Výsledkem procesu serializace je soubor o délce 44 bajtů, což je na tříprvkové pole poměrně vysoká hodnota:

000000 85 c4 02 6e 64 c3 c4 04 74 79 70 65 a3 7c 69 31  >...nd...type.|i1<
000010 c4 04 6b 69 6e 64 c4 00 c4 05 73 68 61 70 65 91  >..kind....shape.<
000020 03 c4 04 64 61 74 61 c4 03 01 02 03              >...data.....<
00002c

Soubor začíná kódem 85, což značí mapu s pěti dvojicemi klíč-hodnota. Konkrétně se jedná o dvojice:

Klíč Hodnota Typ hodnoty
„nd“ true boolean
„type“ |i1 string (typ prvků dle NumPy)
„kind“ [] prázdný řetězec
„shape“ [3] pole s jediným prvkem
„data“ [1, 2, 3] pole se třemi krátkým celými čísly

Konkrétní význam jednotlivých bajtů není složité rozluštit:

Bajty Význam
85 mapa s pěti dvojicemi klíč-hodnota
c4 02 6e 64 klíč „nd“
c3 hodnota True
c4 04 74 79 70 65 klíč „type“
a3 7c 69 31 hodnota „|i1“
c4 04 6b 69 6e 64 klíč „kind“ (jako pole bajtů)
c4 00 prázdný řetězec (jako pole bajtů)
c4 05 73 68 61 70 65 klíč „shape“ (jako pole bajtů)
91 03 pole s jediným prvkem [3]
c4 04 64 61 74 61 klíč „data“ (jako pole bajtů)
c4 03 01 02 03 [1, 2, 3]

Ve skutečnosti však velikost souboru roste (až na hlavičku) prakticky lineárně s délkou pole. To si ostatně můžeme snadno ověřit, a to na poli s deseti tisíci prvky typu float32. Čistá data tohoto pole zabírají 10000×sizeof(float32)=40000 bajtů:

import msgpack
import msgpack_numpy as m
 
import numpy as np
 
m.patch()
 
value = np.zeros([10000], dtype=np.float32)
print(value.shape)
 
with open("array_1d_large.bin", "wb") as outfile:
    packed = msgpack.packb(value)
    outfile.write(packed)

Výsledný soubor, který si zde pochopitelně celý ukazovat nebudeme, má délku 40044 bajtů, což znamená, že přidáno bylo pouze 44 bajtů s metainformacemi.

18. Příloha: alternativní binární formáty pro serializaci dat

Jak jsme se již zmínili v úvodní kapitole, existuje ve skutečnosti mnohem větší množství binárních formátů používaných jak pro serializaci dat, tak i pro komunikaci mezi různými službami (resp. přesněji řečeno pro posílání dat/zpráv mezi těmito službami nebo mikroslužbami). Alespoň krátce se tedy o některých z těchto formátů zmiňme.

Prvním alternativním binárním formátem, s nímž se v oblasti mikroslužeb setkáme, je formát nazvaný gob neboli Go Objects. Jedná se o formát určený primárně pro použití v programovacím jazyku Go, což znamená, že jeho využití je relativně specifické (ukládání rozsáhlých dat, komunikace mezi dvojicí služeb naprogramovaných v Go atd.). Tento formát umožňuje serializaci prakticky jakékoli datové struktury, ovšem je ho možné použít i pro primitivní datové typy, resp. pro jejich hodnoty. Pro Python existuje balíček umožňující s gob pracovat, což je v heterogenních systémech (každá mikroslužba může být naprogramována v jiném jazyce) užitečné.

Dalším binárním formátem určeným pro přenos prakticky libovolně strukturovaných dat je formát nazvaný CBOR neboli plným jménem Concise Binary Object Representation. Tímto formátem, jenž se snaží nabízet podobné vlastnosti jako JSON (až na možnost jeho přímého čtení člověkem), se budeme zabývat v navazujícím textu (interně je nepatrně složitější než MessagePack, navíc z MessagePacku vychází).

Dalším sice relativně novým, ale postupně se rozšiřujícím binárním formátem je formát nazvaný BSON (zde je odkaz na JSON nesporný). Možnosti tohoto formátu jsou již větší, například je podporován typ decimal128 určený pro použití v bankovnictví. Taktéž podporuje uložení časových razítek nebo i kódu v JavaScriptu.

Poznámka: zajímavé je, že ani jeden z uvedených binárních formátů nepodporuje typ bfloat16, i když zrovna v této oblasti se jeho použití přímo nabízí. Toto omezení v MessagePacku obcházíme vlastními datovými typy.

19. Repositář s demonstračními příklady

Všechny demonstrační příklady využívající knihovnu PyTorch lze nalézt v repositáři https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady:

# Příklad Stručný popis Adresa příkladu
1 import_msgpack.py import balíčku msgpack se zobrazením nápovědy https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/im­port_msgpack.py
2 serialize_none.py serializace hodnoty None do souboru https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_none.py
3 serialize_true.py serializace pravdivostní hodnoty True https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_true.py
4 serialize_false.py serializace pravdivostní hodnoty False https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_false.py
       
5 serialize_int_tiny.py serializace malé celočíselné hodnoty do jediného bajtu https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_int_tiny.py
6 serialize_int_small.py serializace celočíselné hodnoty do dvou bajtů https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_int_small.py
7 serialize_int_large.py serializace celočíselné hodnoty do tří bajtů https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_int_large.py
8 serialize_int_larger.py serializace celočíselné hodnoty do pěti bajtů https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_int_larger.py
9 serialize_int_long.py serializace celočíselné hodnoty do devíti bajtů https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_int_long.py
10 serialize_too_long_int.py pokus o serializace hodnoty přesahující 263 https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_too_long_int.py
11 serialize_negative_int.py serializace záporné celočíselné hodnoty https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_negative_int.py
       
12 serialize_float.py serializace hodnoty double float https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_float.py
13 serialize_inf.py serializace kladného nekonečna https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_inf.py
14 negative_inf.py serializace záporného nekonečna https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/ne­gative_inf.py
15 serialize_nan.py serializace hodnoty NaN https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_nan.py
       
16 serialize_string_short.py serializace velmi krátkého řetězce https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_string_short.py
17 serialize_string_longer.py serializace delšího řetězce https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_string_longer.py
18 serialize_string_even_longer.py serializace řetězce delšího než 255 bajtů https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_string_even_longer­.py
19 serialize_string_huge.py serializace řetězce delšího než 65535 bajtů https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_string_huge.py
       
20 serialize_array_small.py serializace pole s malým počtem prvků (menších než 255) https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_array_small.py
21 serialize_array_normal.py serializace pole s malým počtem prvků https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_array_normal.py
22 serialize_array_large.py serializace pole s počtem prvků větším než 256 https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_array_large.py
       
23 serialize_dict.py serializace slovníku https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_dict.py
       
24 serialize_numpy_array.py serializace malého N-dimenzionálního pole https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_numpy_array.py
25 serialize_numpy_large_array.py serializace N-dimenzionálního pole s prvky typu float https://github.com/tisnik/most-popular-python-libs/blob/master/msgpack/se­rialize_numpy_large_array­.py

20. Odkazy na Internetu

  1. Základní informace o MessagePacku
    https://msgpack.org/
  2. Balíček msgpack na PyPi
    https://pypi.org/project/msgpack/
  3. MessagePack na Wikipedii
    https://en.wikipedia.org/wi­ki/MessagePack
  4. Comparison of data-serialization formats (Wikipedia)
    https://en.wikipedia.org/wi­ki/Comparison_of_data-serialization_formats
  5. Repositáře msgpacku
    https://github.com/msgpack
  6. Specifikace ukládání různých typů dat
    https://github.com/msgpac­k/msgpack/blob/master/spec­.md
  7. Podpora MessagePacku v různých programovacích jazycích
    https://msgpack.org/#languages
  8. Základní implementace formátu msgpack pro programovací jazyk Go
    https://github.com/msgpack/msgpack-go
  9. go-codec
    https://github.com/ugorji/go
  10. Gobs of data (odlišný serializační formát)
    https://blog.golang.org/gobs-of-data
  11. Formát BSON (odlišný serializační formát)
    http://bsonspec.org/
  12. Problematika nulových hodnot v Go, aneb proč nil != nil
    https://www.root.cz/clanky/pro­blematika-nulovych-hodnot-v-go-aneb-proc-nil-nil/
  13. IEEE-754 Floating Point Converter
    https://www.h-schmidt.net/FloatConverter/I­EEE754.html
  14. Base Convert: IEEE 754 Floating Point
    https://baseconvert.com/ieee-754-floating-point
  15. Brain Floating Point – nový formát uložení čísel pro strojové učení a chytrá čidla
    https://www.root.cz/clanky/brain-floating-point-ndash-novy-format-ulozeni-cisel-pro-strojove-uceni-a-chytra-cidla/
  16. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  17. Protocol Buffers
    https://protobuf.dev/
  18. Protocol Buffers
    https://en.wikipedia.org/wi­ki/Protocol_Buffers
  19. What is the difference between Serialization and Marshaling?
    https://stackoverflow.com/qu­estions/770474/what-is-the-difference-between-serialization-and-marshaling
  20. Comparison of data-serialization formats
    https://en.wikipedia.org/wi­ki/Comparison_of_data-serialization_formats
Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.

'; document.getElementById('preroll-iframe').onload = function () { setupIframe(); } prerollContainer = document.getElementsByClassName('preroll-container-iframe')[0]; } function setupIframe() { prerollDocument = document.getElementById('preroll-iframe').contentWindow.document; let el = prerollDocument.createElement('style'); prerollDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:20px;right:25px}"; videoContent = prerollDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('PREROLL sound allowed'); // setUpIMA(true); videoContent.volume = 1; videoContent.muted = false; setUpIMA(); }).catch(function () { console.log('PREROLL sound forbidden'); videoContent.volume = 0; videoContent.muted = true; setUpIMA(); }); } } function setupDimensions() { prerollWidth = Math.min(iinfoPrerollPosition.offsetWidth, 480); prerollHeight = Math.min(iinfoPrerollPosition.offsetHeight, 320); } function setUpIMA() { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Preroll advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = prerollWidth; // adsRequest.linearAdSlotHeight = prerollHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. prerollDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( prerollDocument.getElementById('adContainer'), videoContent); } function unmutePrerollAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); prerollDocument.getElementById('adMuteBtn').innerHTML = ''; } } function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(prerollWidth, prerollHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } function onAdEvent(adEvent) { const ad = adEvent.getAd(); console.log('Preroll event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: if (!ad.isLinear()) { videoContent.play(); } prerollDocument.getElementById('adContainer').style.width = '100%'; prerollDocument.getElementById('adContainer').style.maxWidth = '640px'; prerollDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); if (ad.isLinear()) { intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } prerollDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (prerollLastError === 303) { playYtVideo(); } break; case google.ima.AdEvent.Type.COMPLETE: if (ad.isLinear()) { clearInterval(intervalTimer); } playYtVideo(); break; } } function onAdError(adErrorEvent) { console.log(adErrorEvent.getError()); prerollLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { playYtVideo(); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoPrerollPosition.remove(); playPrerollAd(); } else { return false; } adVolume = 1; return true; } function onContentPauseRequested() { videoContent.pause(); } function onContentResumeRequested() { videoContent.play(); } function onActiveView() { if (prerollContainer) { const containerOffset = prerollContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (prerollPaused) { adsManager.resume(); prerollPaused = false; } return true; } else { if (!prerollPaused) { adsManager.pause(); prerollPaused = true; } } } return false; } function playYtVideo() { iinfoPrerollPosition.remove(); youtubeIframe.style.display = 'block'; youtubeIframe.src += '&autoplay=1&mute=1'; } }
'; document.getElementById('outstream-iframe').onload = function () { setupIframe(); } replayScreen = document.getElementById('iinfoOutstreamReplay'); iinfoOutstreamPosition = document.getElementById('iinfoOutstreamPosition'); outstreamContainer = document.getElementsByClassName('outstream-container')[0]; setupReplayScreen(); } function setupIframe() { outstreamDocument = document.getElementById('outstream-iframe').contentWindow.document; let el = outstreamDocument.createElement('style'); outstreamDocument.head.appendChild(el); el.innerText = "#adContainer>div:nth-of-type(1),#adContainer>div:nth-of-type(1) > iframe { width: 99% !important;height: 99% !important;max-width: 100%;}#videoContent,body{ width:100vw;height:100vh}body{ font-family:'Helvetica Neue',Arial,sans-serif}#videoContent{ overflow:hidden;background:#000}#adMuteBtn{ width:35px;height:35px;border:0;background:0 0;display:none;position:absolute;fill:rgba(230,230,230,1);bottom:-5px;right:25px}"; videoContent = outstreamDocument.getElementById('contentElement'); videoContent.style.display = 'none'; videoContent.volume = 1; videoContent.muted = false; if ( location.href.indexOf('rejstriky.finance.cz') !== -1 || location.href.indexOf('finance-rejstrik') !== -1 || location.href.indexOf('firmy.euro.cz') !== -1 || location.href.indexOf('euro-rejstrik') !== -1 || location.href.indexOf('/rejstrik/') !== -1 || location.href.indexOf('/rejstrik-firem/') !== -1) { outstreamDirectPlayed = true; soundAllowed = true; iinfoVastUrlIndex = 0; } if (!outstreamDirectPlayed) { console.log('OUTSTREAM direct'); setUpIMA(true); } else { if (soundAllowed) { const playPromise = videoContent.play(); if (playPromise !== undefined) { playPromise.then(function () { console.log('OUTSTREAM sound allowed'); setUpIMA(false); }).catch(function () { console.log('OUTSTREAM sound forbidden'); renderBanner(); }); } } else { renderBanner(); } } } function getWrapper() { let articleWrapper = document.querySelector('.rs-outstream-placeholder'); // Outstream Placeholder from RedSys manipulation if (articleWrapper && articleWrapper.style.display !== 'block') { articleWrapper.innerHTML = ""; articleWrapper.style.display = 'block'; } // Don't render OutStream on homepages if (articleWrapper === null) { if (document.querySelector('body.p-index')) { return null; } } if (articleWrapper === null) { articleWrapper = document.getElementById('iinfo-outstream'); } if (articleWrapper === null) { articleWrapper = document.querySelector('.layout-main__content .detail__article p:nth-of-type(6)'); } if (articleWrapper === null) { // Euro, Autobible, Zdravi articleWrapper = document.querySelector('.o-article .o-article__text p:nth-of-type(6)'); } if (articleWrapper === null) { articleWrapper = document.getElementById('sidebar'); } if (!articleWrapper) { console.error("Outstream wrapper of article was not found."); } return articleWrapper; } function setupDimensions() { outstreamWidth = Math.min(iinfoOutstreamPosition.offsetWidth, 480); outstreamHeight = Math.min(iinfoOutstreamPosition.offsetHeight, 320); } /** * Sets up IMA ad display container, ads loader, and makes an ad request. */ function setUpIMA(direct) { google.ima.settings.setDisableCustomPlaybackForIOS10Plus(true); google.ima.settings.setLocale('cs'); google.ima.settings.setNumRedirects(10); // Create the ad display container. createAdDisplayContainer(); // Create ads loader. adsLoader = new google.ima.AdsLoader(adDisplayContainer); // Listen and respond to ads loaded and error events. adsLoader.addEventListener( google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false); adsLoader.addEventListener( google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false); // An event listener to tell the SDK that our content video // is completed so the SDK can play any post-roll ads. const contentEndedListener = function () { adsLoader.contentComplete(); }; videoContent.onended = contentEndedListener; // Request video ads. const adsRequest = new google.ima.AdsRequest(); if (direct) { adsRequest.adTagUrl = directVast; console.log('Outstream DIRECT CAMPAING advert: ' + directVast); videoContent.muted = true; videoContent.volume = 0; outstreamDirectPlayed = true; } else { adsRequest.adTagUrl = iinfoVastUrls[iinfoVastUrlIndex]; console.log('Outstream advert: ' + iinfoVastUrls[iinfoVastUrlIndex]); videoContent.muted = false; videoContent.volume = 1; } // Specify the linear and nonlinear slot sizes. This helps the SDK to // select the correct creative if multiple are returned. // adsRequest.linearAdSlotWidth = outstreamWidth; // adsRequest.linearAdSlotHeight = outstreamHeight; adsRequest.nonLinearAdSlotWidth = 0; adsRequest.nonLinearAdSlotHeight = 0; adsLoader.requestAds(adsRequest); } function setupReplayScreen() { replayScreen.addEventListener('click', function () { iinfoOutstreamPosition.remove(); iinfoVastUrlIndex = 0; outstreamInit(); }); } /** * Sets the 'adContainer' div as the IMA ad display container. */ function createAdDisplayContainer() { // We assume the adContainer is the DOM id of the element that will house // the ads. outstreamDocument.getElementById('videoContent').style.display = 'none'; adDisplayContainer = new google.ima.AdDisplayContainer( outstreamDocument.getElementById('adContainer'), videoContent); } function unmuteAdvert() { adVolume = !adVolume; if (adVolume) { adsManager.setVolume(0.3); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } else { adsManager.setVolume(0); outstreamDocument.getElementById('adMuteBtn').innerHTML = ''; } } /** * Loads the video content and initializes IMA ad playback. */ function playAds() { // Initialize the container. Must be done through a user action on mobile // devices. videoContent.load(); adDisplayContainer.initialize(); // setupDimensions(); try { // Initialize the ads manager. Ad rules playlist will start at this time. adsManager.init(1920, 1080, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will // start at this time; the call will be ignored for ad rules. adsManager.start(); // window.addEventListener('resize', function (event) { // if (adsManager) { // setupDimensions(); // adsManager.resize(outstreamWidth, outstreamHeight, google.ima.ViewMode.NORMAL); // } // }); } catch (adError) { // An error may be thrown if there was a problem with the VAST response. // videoContent.play(); } } /** * Handles the ad manager loading and sets ad event listeners. * @param { !google.ima.AdsManagerLoadedEvent } adsManagerLoadedEvent */ function onAdsManagerLoaded(adsManagerLoadedEvent) { // Get the ads manager. const adsRenderingSettings = new google.ima.AdsRenderingSettings(); adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true; adsRenderingSettings.loadVideoTimeout = 12000; // videoContent should be set to the content video element. adsManager = adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings); // Add listeners to the required events. adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested); adsManager.addEventListener( google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, onContentResumeRequested); adsManager.addEventListener( google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent); // Listen to any additional events, if necessary. adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent); adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent); playAds(); } /** * Handles actions taken in response to ad events. * @param { !google.ima.AdEvent } adEvent */ function onAdEvent(adEvent) { // Retrieve the ad from the event. Some events (for example, // ALL_ADS_COMPLETED) don't have ad object associated. const ad = adEvent.getAd(); console.log('Outstream event: ' + adEvent.type); switch (adEvent.type) { case google.ima.AdEvent.Type.LOADED: // This is the first event sent for an ad - it is possible to // determine whether the ad is a video ad or an overlay. if (!ad.isLinear()) { // Position AdDisplayContainer correctly for overlay. // Use ad.width and ad.height. videoContent.play(); } outstreamDocument.getElementById('adContainer').style.width = '100%'; outstreamDocument.getElementById('adContainer').style.maxWidth = '640px'; outstreamDocument.getElementById('adContainer').style.height = '360px'; break; case google.ima.AdEvent.Type.STARTED: window.addEventListener('scroll', onActiveView); // This event indicates the ad has started - the video player // can adjust the UI, for example display a pause button and // remaining time. if (ad.isLinear()) { // For a linear ad, a timer can be started to poll for // the remaining time. intervalTimer = setInterval( function () { // Example: const remainingTime = adsManager.getRemainingTime(); // adsManager.pause(); }, 300); // every 300ms } outstreamDocument.getElementById('adMuteBtn').style.display = 'block'; break; case google.ima.AdEvent.Type.ALL_ADS_COMPLETED: if (ad.isLinear()) { clearInterval(intervalTimer); } if (outstreamLastError === 303) { if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } } break; case google.ima.AdEvent.Type.COMPLETE: // This event indicates the ad has finished - the video player // can perform appropriate UI actions, such as removing the timer for // remaining time detection. if (ad.isLinear()) { clearInterval(intervalTimer); } if (isBanner) { renderBanner(); } else { replayScreen.style.display = 'flex'; } break; } } /** * Handles ad errors. * @param { !google.ima.AdErrorEvent } adErrorEvent */ function onAdError(adErrorEvent) { // Handle the error logging. console.log(adErrorEvent.getError()); outstreamLastError = adErrorEvent.getError().getErrorCode(); if (!loadNext()) { renderBanner(); } } function renderBanner() { if (isBanner) { console.log('Outstream: Render Banner'); iinfoOutstreamPosition.innerHTML = ""; iinfoOutstreamPosition.style.height = "330px"; iinfoOutstreamPosition.appendChild(bannerDiv); } else { console.log('Outstream: Banner is not set'); } } function loadNext() { iinfoVastUrlIndex++; if (iinfoVastUrlIndex < iinfoVastUrls.length) { iinfoOutstreamPosition.remove(); outstreamInit(); } else { return false; } adVolume = 1; return true; } /** * Pauses video content and sets up ad UI. */ function onContentPauseRequested() { videoContent.pause(); // This function is where you should setup UI for showing ads (for example, // display ad timer countdown, disable seeking and more.) // setupUIForAds(); } /** * Resumes video content and removes ad UI. */ function onContentResumeRequested() { videoContent.play(); // This function is where you should ensure that your UI is ready // to play content. It is the responsibility of the Publisher to // implement this function when necessary. // setupUIForContent(); } function onActiveView() { if (outstreamContainer) { const containerOffset = outstreamContainer.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight/1 && containerOffset.bottom > 0.0) { if (outstreamPaused) { adsManager.resume(); outstreamPaused = false; } return true; } else { if (!outstreamPaused) { adsManager.pause(); outstreamPaused = true; } } } return false; } let outstreamInitInterval; if (typeof cpexPackage !== "undefined") { outstreamInitInterval = setInterval(tryToInitializeOutstream, 100); } else { const wrapper = getWrapper(); if (wrapper) { let outstreamInitialized = false; window.addEventListener('scroll', () => { if (!outstreamInitialized) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { outstreamInit(); outstreamInitialized = true; } } }); } } function tryToInitializeOutstream() { const wrapper = getWrapper(); if (wrapper) { const containerOffset = wrapper.getBoundingClientRect(); const windowHeight = window.innerHeight; if (containerOffset.top < windowHeight / 1 && containerOffset.bottom > 0.0) { if (cpexPackage.adserver.displayed) { clearInterval(outstreamInitInterval); outstreamInit(); } } } else { clearInterval(outstreamInitInterval); } } }
OSZAR »