Balíček array ze standardní knihovny Pythonu

18. 1. 2024
Doba čtení: 29 minut

Sdílet

Autor: Depositphotos
Dnes se seznámíme se standardním balíčkem array, jenž umožňuje práci s homogenními poli. Ta jsou v operační paměti uložena velmi efektivně a mohou tak představovat užitečnou alternativu ke klasickým seznamům.

Obsah

1. Balíček array ze standardní knihovny Pythonu

2. Konstrukce pole s určením typů jeho prvků

3. Podporované typy prvků

4. Obsazení operační paměti Pythonovským objektem

5. Balíček Pympler pro přesnější informace o obsazení paměti Pythonem

6. Obsazení paměti prázdným seznamem popř. prázdným polem

7. Obsazení paměti seznamem popř. polem se sto prvky a 10000 prvky

8. Sekvenční přístup k prvkům seznamů a polí

9. Vložení hodnoty do seznamu či pole na určené místo (operace insert)

10. Interní struktura prvků polí

11. Uložení obsahu polí do souboru

12. Načtení obsahu polí z binárních souborů

13. Složitěji strukturované binární soubory

14. Teoretická a praktická rychlost přístupu k prvkům polí

15. Tentýž výpočet, ovšem realizovaný s využitím polí

16. Interní způsob uložení pole v paměti

17. Algoritmus pro alokaci paměti pro zvětšující a zmenšující se pole

18. Průběh alokace

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

20. Odkazy na Internetu

1. Balíček array ze standardní knihovny Pythonu

Ve standardní knihovně programovacího jazyka Python se nachází množství užitečných ale i méně užitečných balíčků a modulů. V některých případech se může stát užitečným balíček nazvaný jednoduše array. Tento balíček umožňuje paměťově efektivní ukládání homogenních polí, tj. polí, jejichž prvky jsou všechny stejného typu. Tato pole interně obsahují skutečná céčková pole (viz další kapitoly) a uloženy jsou zde přímo hodnoty prvků, nikoli objekty (běžně se totiž v Pythonu pracuje s referencemi, což je z pohledu lokality i obsazení paměti velmi neefektivní řešení).

Navíc mohou pole růst nebo se zmenšovat, lze do nich přidávat prvky na libovolné místo, je podporována operace řezu (slice) (používá se naprosto stejný zápis, jako v případě seznamů) a navíc je možné taková pole ukládat do binárních souborů nebo je naopak z těchto souborů číst. Pole realizovaná v balíčku array je tak možné v odůvodněných případech využít namísto klasických seznamů. Dnes si však ukážeme i některé nedostatky tohoto způsobu reprezentace polí.

Poznámka: alternativou k balíčku array jsou n-dimenzionální pole z knihovny NumPy. Ty však vyžadují instalaci poměrně objemného balíčku, takže pokud je vaším požadavkem například efektivní uložení milionu hodnot typu float (apod.) a nic dalšího, je dnes popisovaný standardní balíček array pravděpodobně výhodnějším řešením.

2. Konstrukce pole s určením typů jeho prvků

Homogenní jednorozměrné pole se při použití standardního balíčku array vytváří konstruktorem nazvaným taktéž array. Tomuto konstruktoru se musí předat jednoznakový kód typu prvků (viz tabulka zobrazená níže) a popř. hodnota, z níž se odvodí prvky pole. Touto hodnotou může být seznam popř. hodnota typu bytes nebo bytearray, tj. neměnitelné popř. naopak měnitelné sekvence bajtů (připomeňme si, že hodnotu typu bytes můžeme v případě potřeby zapsat formou b"znaky").

Příklad konstrukce prázdného pole:

from array import array
 
a1 = array(kód_typu_prvků)

Příklad konstrukce pole se specifikací hodnoty prvků seznamem:

from array import array
 
a1 = array(kód_typu_prvků, [1, 2, 3, 4])

popř.

from array import array
 
a1 = array("B", range(100))

Odlišný příklad konstrukce pole se specifikací hodnoty prvků sekvencí bajtů:

from array import array
 
a2 = array(kód_typu_prvků, b"ABCDEFGH")
Poznámka: v tomto případě musí být počet bajtů v sekvenci celočíselně dělitelný velikostí prvku. Například pro čtyřbajtové prvky (typicky signed int či unsigned int) musí být počet bajtů dělitelný čtyřmi. Ostatně si to můžeme velmi snadno ověřit tak, že budeme vyžadovat vytvoření pole s 16bitovými hodnotami, ale předáme mu v konstruktoru sekvenci bajtů, jejíž délka není celočíselně dělitelná dvěma:
from array import array
 
a2 = array("h", b"ABCDEFG")

V tomto případě dojde k vyhození výjimky:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: bytes length not a multiple of item size

3. Podporované typy prvků

Typ prvků je zakódován v jednoznakovém řetězci, který je předán jako první parametr do konstruktoru array.array. Podporovány jsou následující kódy, které vždy odpovídají jednomu datovému typu jazyka C a a mají (tak) i specifikovánu minimální velikost v bajtech:

Zápis typu Odpovídající typ v C Minimální velikost v B
„b“ signed char 1
„B“ unsigned char 1
„h“ signed short 2
„H“ unsigned short 2
„i“ signed int 2
„I“ unsigned int 2
„l“ signed long 4
„L“ unsigned long 4
„q“ signed long long 8
„Q“ unsigned long long 8
     
„f“ float 4
„d“ double 8

Jen pro zajímavost se podívejme, jaké typy prvků jsou podporovány v knihovně NumPy při konstrukci n-rozměrných polí. Jedná se sice o interně odlišnou datovou strukturu s jiným způsobem použití, ale mnoho kódů je shodných:

Zápis typu Odpovídající typ v C
„b“ signed char
„B“ unsigned char
„h“ signed short
„H“ unsigned short
„i“ signed int
„I“ unsigned int
„l“ signed long
„L“ unsigned long
„q“ signed long long
„Q“ unsigned long long
   
„e“ half float
„f“ float
„d“ double
„g“ long double
   
„F“ complex float
„D“ complex double
„G“ complex long double

4. Obsazení operační paměti Pythonovským objektem

Největší předností (a pravděpodobně i jedinou předností) balíčku array je jeho schopnost efektivně uložit prvky určitého datového typu do kontinuálního bloku operační paměti. To vede resp. mělo by to vést k velkým paměťovým úsporám. Ovšem toto tvrzení je vhodné si ověřit v praxi. Konkrétně tedy budeme porovnávat celkovou velikost seznamu s n-položkami s jednotlivými poli realizovanými balíčkem array.

Velikost, kterou nějaká hodnota zabírá v operační paměti, můžeme získat s využitím standardní funkce sys.getsizeof. Největší (a možná i jedinou) předností této funkce je fakt, že se jedná o funkci ze standardní knihovny sys. To mj. znamená, že tuto funkci je možné zavolat prakticky kdykoli a kdekoli, a to bez nutnosti instalace nějakých dalších balíčků.

Ovšem na tomto místě je vhodné upozornit na to, že tato funkce není příliš dokonalá, protože například neumožňuje zjistit velikost hodnot v operační paměti po zarovnání, protože alokátor paměti alokuje nové bloky vždy na adresách dělitelných nějakou konstantou 4, 8 či 16 (podle použitého operačního systému a architektury mikroprocesoru) a tudíž vlastně nebudou využity bajty umístěné těsně za koncem bloku v případě, že velikost bloku není dělitelná výše uvedenou konstantou. A navíc má tato standardní funkce problémy se zjištěním velikosti tříd a objektů, protože automaticky nezapočítává velikosti jednotlivých atributů atd. A totéž platí i pro kolekce, mezi jinými i pro seznamy.

5. Balíček Pympler pro přesnější informace o obsazení paměti Pythonem

Existují však i další (ovšem nutno dodat, že již nestandardní) balíčky, které „opravují“ chování funkce sys.getsizeof a navíc nabízí uživatelům i další zajímavou a potenciálně užitečnou funkcionalitu. Tyto balíčky je však pochopitelně nutné nejdříve nainstalovat. Prvním z balíčků, o nichž se dnes alespoň ve stručnosti zmíníme, je balíček nazvaný Pympler. Vzhledem k tomu, že je zaregistrovaný na PyPi, je instalace tohoto balíčku triviální a můžeme ji provést (pro právě přihlášeného uživatele) tímto příkazem:

$ pip install --user pympler

Alternativně pro systémy, které ještě mají paralelní instalaci Pythonu 2 a Pythonu 3, použijeme příkaz:

$ pip3 install --user pympler

Samotná instalace proběhne prakticky okamžitě:

Collecting pympler
  Downloading Pympler-1.0.1-py3-none-any.whl (164 kB)
     |████████████████████████████████| 164 kB 1.5 MB/s
Installing collected packages: pympler
Successfully installed pympler-1.0.1
Poznámka: samozřejmě můžete provést instalaci i tohoto balíčku v rámci virtuálního prostředí Pythonu atd.

6. Obsazení paměti prázdným seznamem popř. prázdným polem

V dnešním prvním demonstračním příkladu zjistíme, jak velkou kapacitu operační paměti musíme použít pro uložení prázdného seznamu (což je standardní kolekce Pythonu) nebo prázdného pole s prvky zvoleného typu. Vytvoříme tedy prázdný seznam konstruktorem:

l1 = []

a několik prázdných polí určených pro uložení prvků různého typu:

a1 = array("B")
a2 = array("H")
a3 = array("I")
a4 = array("L")
a5 = array("Q")
a6 = array("f")
a7 = array("d")

Následně si s využitím funkce asizeof.asizeof necháme vypočítat velikost obsazené paměti:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from pympler.asizeof import asizeof
 
from array import array
 
# prázdné struktury
l1 = []
a1 = array("B")
a2 = array("H")
a3 = array("I")
a4 = array("L")
a5 = array("Q")
a6 = array("f")
a7 = array("d")
 
# výpis skutečných velikostí
print("List:             ", asizeof(l1))
print("Array of bytes:   ", asizeof(a1))
print("Array of shorts:  ", asizeof(a2))
print("Array of ints:    ", asizeof(a3))
print("Array of longs:   ", asizeof(a4))
print("Array of quads:   ", asizeof(a5))
print("Array of floats:  ", asizeof(a6))
print("Array of doubles: ", asizeof(a7))

Výsledky budou vypadat následovně (hodnoty jsou reprezentovány v bajtech):

List:              56
Array of bytes:    64
Array of shorts:   64
Array of ints:     64
Array of longs:    64
Array of quads:    64
Array of floats:   64
Array of doubles:  64
Poznámka: v tomto případě sice vyhrál klasický seznam, ovšem stále se jedná o prázdné kolekce, takže tato výhra v praxi většinou nic neznamená. Navíc se později zmíníme o tom, jak je vlastně alokována paměť pro pole – zde se skrývá několik zajímavých detailů.

7. Obsazení paměti seznamem popř. polem se sto prvky a 10000 prvky

Nyní se pokusíme zjistit, kolik paměti bude obsazeno seznamem a poli se sto prvky a posléze s tisíci prvky. Zdrojový kód příkladu upravíme následujícím způsobem (povšimněte si, jak se do polí přidávají další prvky):

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from pympler.asizeof import asizeof
from array import array
 
# prázdné struktury
l1 = []
a1 = array("B")
a2 = array("H")
a3 = array("I")
a4 = array("L")
a5 = array("Q")
a6 = array("f")
a7 = array("d")
 
# naplnit daty
for i in range(100):
    l1.append(i)
    a1.append(i)
    a2.append(i)
    a3.append(i)
    a4.append(i)
    a5.append(i)
    a6.append(i)
    a7.append(i)
 
# výpis skutečných velikostí
print("List:             ", asizeof(l1))
print("Array of bytes:   ", asizeof(a1))
print("Array of shorts:  ", asizeof(a2))
print("Array of ints:    ", asizeof(a3))
print("Array of longs:   ", asizeof(a4))
print("Array of quads:   ", asizeof(a5))
print("Array of floats:  ", asizeof(a6))
print("Array of doubles: ", asizeof(a7))

Výsledky pro stoprvkové kolekce:

List:              4096
Array of bytes:    168
Array of shorts:   272
Array of ints:     472
Array of longs:    880
Array of quads:    880
Array of floats:   472
Array of doubles:  880

Pro pole s tisíci hodnotami nepatrně upravíme programový kód určený pro vkládání prvků, protože do polí s hodnotami typu bajt je možné uložit jen hodnoty s omezeným rozsahem (127 resp. 255):

for i in range(10000):
    l1.append(i)
    a1.append(i % 100)
    a2.append(i % 100)
    a3.append(i)
    a4.append(i)
    a5.append(i)
    a6.append(i)
    a7.append(i)

Výsledky pro kolekce s 10000 prvky:

List:              407608
Array of bytes:    10152
Array of shorts:   20240
Array of ints:     40408
Array of longs:    80744
Array of quads:    80744
Array of floats:   40408
Array of doubles:  80744

Z výsledků je patrné, že u seznamů roste spotřeba paměti poměrně výrazně, což by ale nemělo být překvapivé, protože hodnoty jsou zde boxovány. Zajímavé ovšem je, že i u polí je obsazená paměť větší, než by to odpovídalo výpočtu počet_prvků×velikost_jednoho prvku. U rozsáhlejších polí je „nadbytečná“ spotřeba paměti rovna přibližně jednomu až třem procentům, takže se většinou nejedná o výrazný problém. A opět si ještě řekneme, proč tomu tak je.

8. Sekvenční přístup k prvkům seznamů a polí

V případě, že použijeme standardní seznamy, je sekvenční průchod prvky seznamů v Pythonu realizován tímto idiomatickým zápisem:

lst = ["foo", "bar", "baz"]
 
for item in lst:
    print(item)
Poznámka: pochopitelně je možné použít i další způsoby, ovšem ty nejsou příliš často k vidění.

Naprosto stejným způsobem je možné procházet i prvky polí, což je velmi rozumné. Příkladem může být průchod polem se čtyřmi prvky typu „bajt“:

arr = array("B", [1, 2, 3, 4])
 
for item in arr:
    print(item)

Všemi typy polí se prochází naprosto stejným způsobem, nezávisle na typu jejich prvků:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from array import array
 
# prázdné struktury
l1 = []
a1 = array("B")
a2 = array("H")
a3 = array("I")
a4 = array("L")
a5 = array("Q")
a6 = array("f")
a7 = array("d")
 
# naplnit daty
for i in range(100):
    l1.append(i)
    a1.append(i)
    a2.append(i)
    a3.append(i)
    a4.append(i)
    a5.append(i)
    a6.append(i)
    a7.append(i)
 
 
def print_all_items(name, struct):
    """Vytištění všech prvků uložených ve struktuře."""
    print(name)
    for item in struct:
        print(item, end=" ")
    print("\n")
 
 
print_all_items("List:", l1)
print_all_items("Array of bytes:", a1)
print_all_items("Array of shorts:", a2)
print_all_items("Array of ints:", a3)
print_all_items("Array of longs:", a4)
print_all_items("Array of quads:", a5)
print_all_items("Array of floats:", a6)
print_all_items("Array of doubles:", a7)

Výsledky zobrazené tímto příkladem na terminálu:

List:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
 
Array of bytes:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
 
Array of shorts:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
 
Array of ints:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
 
Array of longs:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
 
Array of quads:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
 
Array of floats:
0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 21.0 22.0 23.0 24.0 25.0 26.0 27.0 28.0 29.0 30.0 31.0 32.0 33.0 34.0 35.0 36.0 37.0 38.0 39.0 40.0 41.0 42.0 43.0 44.0 45.0 46.0 47.0 48.0 49.0 50.0 51.0 52.0 53.0 54.0 55.0 56.0 57.0 58.0 59.0 60.0 61.0 62.0 63.0 64.0 65.0 66.0 67.0 68.0 69.0 70.0 71.0 72.0 73.0 74.0 75.0 76.0 77.0 78.0 79.0 80.0 81.0 82.0 83.0 84.0 85.0 86.0 87.0 88.0 89.0 90.0 91.0 92.0 93.0 94.0 95.0 96.0 97.0 98.0 99.0
 
Array of doubles:
0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 21.0 22.0 23.0 24.0 25.0 26.0 27.0 28.0 29.0 30.0 31.0 32.0 33.0 34.0 35.0 36.0 37.0 38.0 39.0 40.0 41.0 42.0 43.0 44.0 45.0 46.0 47.0 48.0 49.0 50.0 51.0 52.0 53.0 54.0 55.0 56.0 57.0 58.0 59.0 60.0 61.0 62.0 63.0 64.0 65.0 66.0 67.0 68.0 69.0 70.0 71.0 72.0 73.0 74.0 75.0 76.0 77.0 78.0 79.0 80.0 81.0 82.0 83.0 84.0 85.0 86.0 87.0 88.0 89.0 90.0 91.0 92.0 93.0 94.0 95.0 96.0 97.0 98.0 99.0

9. Vložení hodnoty do seznamu či pole na určené místo (operace insert)

V předchozích demonstračních příkladech jsme prvky do seznamu a polí připojovali, což je u obou zmíněných datových struktur realizováno operací se stejným jménem – append. Díky tomu, že prvky byly připojovány vždy až na konec pole (přesněji řečeno za konec pole), nebylo nutné specifikovat index přidávaného prvku. Ovšem jinak je tomu u operace vkládaní neboli insert. V tomto případě specifikujeme index, do kterého bude nový prvek přidán (a zbytek prvků bude odsunut). Pravděpodobně je zřejmé, že se jedná o obecně časově náročnou operaci, která může vyžadovat jak realokaci samotného pole, tak i přesuny prvků. I operace insert má stejnou sémantiku pro seznamy i pro pole včetně kontroly hodnoty indexu, možnosti použití záporných indexů (pro indexování zprava) atd.

Seznamy:

l = ["foo", "bar"]
 
l.insert(0, "baz")
print(l)
['baz', 'foo', 'bar']
 
l.insert(3, "END")
print(l)
['baz', 'foo', 'bar', 'END']
 
l.insert(-1, ".")
print(l)
['baz', 'foo', 'bar', '.', 'END']
 
l.insert(1000, "not-possible?")
print(l)
['baz', 'foo', 'bar', '.', 'END', 'not-possible?']

Pole:

a = array("B", [10, 20])
 
a.insert(0, 5)
print(a)
array('B', [5, 10, 20])
 
a.insert(3, 30)
print(a)
array('B', [5, 10, 20, 30])
 
a.insert(-1, 0)
print(a)
array('B', [5, 10, 20, 0, 30])
 
a.insert(10000, 40)
print(a)
array('B', [5, 10, 20, 0, 30, 40])

To znamená, že jak pole tak seznamy můžeme naplnit operací „připojení zleva“, protože sémantika insert(0, hodnota) vlastně odpovídá sémantice append(hodnota) v případě, pokud by se prvky připojovaly před začátek sekvence a nikoli za konec sekvence:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from array import array
 
# prázdné struktury
l1 = []
a1 = array("B")
a2 = array("H")
a3 = array("I")
a4 = array("L")
a5 = array("Q")
a6 = array("f")
a7 = array("d")
 
# naplnit daty
for i in range(100):
    l1.insert(0, i)
    a1.insert(0, i)
    a2.insert(0, i)
    a3.insert(0, i)
    a4.insert(0, i)
    a5.insert(0, i)
    a6.insert(0, i)
    a7.insert(0, i)
 
 
def print_all_items(name, struct):
    """Vytištění všech prvků uložených ve struktuře."""
    print(name)
    for item in struct:
        print(item, end=" ")
    print("\n")
 
 
print_all_items("List:", l1)
print_all_items("Array of bytes:", a1)
print_all_items("Array of shorts:", a2)
print_all_items("Array of ints:", a3)
print_all_items("Array of longs:", a4)
print_all_items("Array of quads:", a5)
print_all_items("Array of floats:", a6)
print_all_items("Array of doubles:", a7)

S výsledky, které nejsou ničím překvapující:

List:
99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
 
Array of bytes:
99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
 
Array of shorts:
99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
 
Array of ints:
99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
 
Array of longs:
99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
 
Array of quads:
99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
 
Array of floats:
99.0 98.0 97.0 96.0 95.0 94.0 93.0 92.0 91.0 90.0 89.0 88.0 87.0 86.0 85.0 84.0 83.0 82.0 81.0 80.0 79.0 78.0 77.0 76.0 75.0 74.0 73.0 72.0 71.0 70.0 69.0 68.0 67.0 66.0 65.0 64.0 63.0 62.0 61.0 60.0 59.0 58.0 57.0 56.0 55.0 54.0 53.0 52.0 51.0 50.0 49.0 48.0 47.0 46.0 45.0 44.0 43.0 42.0 41.0 40.0 39.0 38.0 37.0 36.0 35.0 34.0 33.0 32.0 31.0 30.0 29.0 28.0 27.0 26.0 25.0 24.0 23.0 22.0 21.0 20.0 19.0 18.0 17.0 16.0 15.0 14.0 13.0 12.0 11.0 10.0 9.0 8.0 7.0 6.0 5.0 4.0 3.0 2.0 1.0 0.0
 
Array of doubles:
99.0 98.0 97.0 96.0 95.0 94.0 93.0 92.0 91.0 90.0 89.0 88.0 87.0 86.0 85.0 84.0 83.0 82.0 81.0 80.0 79.0 78.0 77.0 76.0 75.0 74.0 73.0 72.0 71.0 70.0 69.0 68.0 67.0 66.0 65.0 64.0 63.0 62.0 61.0 60.0 59.0 58.0 57.0 56.0 55.0 54.0 53.0 52.0 51.0 50.0 49.0 48.0 47.0 46.0 45.0 44.0 43.0 42.0 41.0 40.0 39.0 38.0 37.0 36.0 35.0 34.0 33.0 32.0 31.0 30.0 29.0 28.0 27.0 26.0 25.0 24.0 23.0 22.0 21.0 20.0 19.0 18.0 17.0 16.0 15.0 14.0 13.0 12.0 11.0 10.0 9.0 8.0 7.0 6.0 5.0 4.0 3.0 2.0 1.0 0.0

10. Interní struktura prvků polí

Všechny prvky pole tak, jak jsou uloženy v paměti, je možné si nechat vypsat ve formě sekvence hexadecimálních hodnot. Tak například zjistíme, jakým způsobem jsou uloženy celočíselné hodnoty širší než jeden bajt, jak jsou uloženy hodnoty s plovoucí řádovou čárkou a samozřejmě je taktéž možné rozlišit mezi způsoby uložení vícebajtových hodnot: little endian či big endian. Vytvoříme si tedy pomocnou funkci, která získá jako svůj parametr libovolné pole a následně vypíše jeho obsah v hexadecimální formě na standardní výstup:

def print_all_items_as_hex(name, struct):
    print(name)
    print(struct.tobytes().hex(" "))
    print()

Tuto funkci následně zavoláme a postupně jí předáme pole všech typů, abychom mohli prozkoumat případné rozdíly:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from array import array
 
# prázdné struktury
a1 = array("B")
a2 = array("H")
a3 = array("I")
a4 = array("L")
a5 = array("Q")
a6 = array("f")
a7 = array("d")
 
# naplnit daty
for i in range(10):
    a1.append(i)
    a2.append(i)
    a3.append(i)
    a4.append(i)
    a5.append(i)
    a6.append(i)
    a7.append(i)
 
 
def print_all_items_as_hex(name, struct):
    print(name)
    print(struct.tobytes().hex(" "))
    print()
 
 
print_all_items_as_hex("Array of bytes:", a1)
print_all_items_as_hex("Array of shorts:", a2)
print_all_items_as_hex("Array of ints:", a3)
print_all_items_as_hex("Array of longs:", a4)
print_all_items_as_hex("Array of quads:", a5)
print_all_items_as_hex("Array of floats:", a6)
print_all_items_as_hex("Array of doubles:", a7)

Pole bajtů je nejjednodušší – prvky jsou uloženy za sebou bez jakýchkoli výplní:

Array of bytes:
00 01 02 03 04 05 06 07 08 09

U polí obsahujících „širší“ celočíselné hodnoty je patrné, že je na počítači s architekturou x86–64 použit little endian:

Array of shorts:
00 00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 09 00
 
Array of ints:
00 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00 09 00 00 00
 
Array of longs:
00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00
00 00 00 00 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00
05 00 00 00 00 00 00 00 06 00 00 00 00 00 00 00 07 00 00 00
00 00 00 00 08 00 00 00 00 00 00 00 09 00 00 00 00 00 00 00
 
Array of quads:
00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00
00 00 00 00 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00
05 00 00 00 00 00 00 00 06 00 00 00 00 00 00 00 07 00 00 00
00 00 00 00 08 00 00 00 00 00 00 00 09 00 00 00 00 00 00 00

A konečně jsou vypsány i prvky typu float a double:

Array of floats:
00 00 00 00 00 00 80 3f 00 00 00 40 00 00 40 40 00 00 80 40
00 00 a0 40 00 00 c0 40 00 00 e0 40 00 00 00 41 00 00 10 41
 
Array of doubles:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 f0 3f 00 00 00 00
00 00 00 40 00 00 00 00 00 00 08 40 00 00 00 00 00 00 10 40
00 00 00 00 00 00 14 40 00 00 00 00 00 00 18 40 00 00 00 00
00 00 1c 40 00 00 00 00 00 00 20 40 00 00 00 00 00 00 22 40

11. Uložení obsahu polí do souboru

Obsah polí lze uložit (serializovat) do souboru. Jako výsledek získáme binární soubory, které nebudou obsahovat žádnou hlavičku, výplně, ani žádné další doplňují struktury. To znamená, že velikost výsledného binárního souboru bude striktně odpovídat počtu prvků vynásobenou šířkou prvků v bajtech. Ostatně se o tom můžeme velmi snadno přesvědčit. Pro uložení pole do souboru použijeme metodu tofile, které se předá handle otevřeného souboru:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from array import array
 
# prázdné struktury
a1 = array("B")
a2 = array("H")
a3 = array("I")
a4 = array("L")
a5 = array("Q")
a6 = array("f")
a7 = array("d")
 
# naplnit daty
for i in range(100):
    a1.append(i)
    a2.append(i)
    a3.append(i)
    a4.append(i)
    a5.append(i)
    a6.append(i)
    a7.append(i)
 
 
def export_to_file(filename, struct):
    """Export celé struktury do souboru."""
    with open(filename, "wb") as f:
        struct.tofile(f)
 
 
export_to_file("bytes.bin", a1)
export_to_file("shorts.bin", a2)
export_to_file("ints.bin", a3)
export_to_file("longs.bin", a4)
export_to_file("quads.bin", a5)
export_to_file("floats.bin", a6)
export_to_file("doubles.bin", a7)

Podívejme se nyní na velikosti výsledných souborů. Pro větší přehlednost jsme si nechali soubory seřadit podle jejich délky:

$ ls -lSr *.bin

Výsledky:

-rw-rw-r-- 1 ptisnovs ptisnovs 100 Dec 29 16:05 bytes.bin
-rw-rw-r-- 1 ptisnovs ptisnovs 200 Dec 29 16:05 shorts.bin
-rw-rw-r-- 1 ptisnovs ptisnovs 400 Dec 29 16:05 ints.bin
-rw-rw-r-- 1 ptisnovs ptisnovs 400 Dec 29 16:05 floats.bin
-rw-rw-r-- 1 ptisnovs ptisnovs 800 Dec 29 16:05 quads.bin
-rw-rw-r-- 1 ptisnovs ptisnovs 800 Dec 29 16:05 longs.bin
-rw-rw-r-- 1 ptisnovs ptisnovs 800 Dec 29 16:05 doubles.bin

Pro zajímavost si prohlédněme alespoň soubor shorts.bin, který obsahuje pole se 100 prvky typu short (šestnáctibitové hodnoty):

$ od -tx1 shorts.bin 
 
0000000 00 00 01 00 02 00 03 00 04 00 05 00 06 00 07 00
0000020 08 00 09 00 0a 00 0b 00 0c 00 0d 00 0e 00 0f 00
0000040 10 00 11 00 12 00 13 00 14 00 15 00 16 00 17 00
0000060 18 00 19 00 1a 00 1b 00 1c 00 1d 00 1e 00 1f 00
0000100 20 00 21 00 22 00 23 00 24 00 25 00 26 00 27 00
0000120 28 00 29 00 2a 00 2b 00 2c 00 2d 00 2e 00 2f 00
0000140 30 00 31 00 32 00 33 00 34 00 35 00 36 00 37 00
0000160 38 00 39 00 3a 00 3b 00 3c 00 3d 00 3e 00 3f 00
0000200 40 00 41 00 42 00 43 00 44 00 45 00 46 00 47 00
0000220 48 00 49 00 4a 00 4b 00 4c 00 4d 00 4e 00 4f 00
0000240 50 00 51 00 52 00 53 00 54 00 55 00 56 00 57 00
0000260 58 00 59 00 5a 00 5b 00 5c 00 5d 00 5e 00 5f 00
0000300 60 00 61 00 62 00 63 00
0000310
Poznámka: z tohoto výsledku je patrné, že i zde je použito uspořádání bajtů little endian, což může způsobit problémy při zpracování těchto binárních souborů na architekturách s uspořádáním bajtů big endian.

12. Načtení obsahu polí z binárních souborů

Opakem operace pro uložení pole do binárního souboru je pochopitelně operace, která toto pole naopak ze souboru načte. Pro tento účel je nejdříve nutné pole zkonstruovat (může být ovšem i prázdné) a následně jeho obsah načíst ze souboru metodou fromfile. Této metodě se kromě handle otevřeného souboru předává i počet prvků, které se mají načíst. Soubor musí obsahovat potřebná data; v opačném případě dojde k vyhození výjimky:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from array import array
 
# prázdné struktury
a1 = array("B")
a2 = array("H")
a3 = array("I")
a4 = array("L")
a5 = array("Q")
a6 = array("f")
a7 = array("d")
 
 
def import_from_file(filename, struct):
    """Import celé struktury ze souboru."""
    with open(filename, "rb") as f:
        struct.fromfile(f, 100)
    return struct
 
 
print(import_from_file("bytes.bin", a1))
print(import_from_file("shorts.bin", a2))
print(import_from_file("ints.bin", a3))
print(import_from_file("longs.bin", a4))
print(import_from_file("quads.bin", a5))
print(import_from_file("floats.bin", a6))
print(import_from_file("doubles.bin", a7))

Z vypsaných výsledků je patrné, že se všechna pole korektně načetla:

array('B', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99])
 
array('H', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99])
 
array('I', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99])
 
array('L', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99])
 
array('Q', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99])
 
array('f', [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0,
11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0,
22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0,
33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0,
44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0,
55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, 65.0,
66.0, 67.0, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0,
77.0, 78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0,
88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0, 99.0])
 
array('d', [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0,
11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0,
22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0,
33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0,
44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0,
55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, 65.0,
66.0, 67.0, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0,
77.0, 78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0,
88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0, 99.0])

13. Složitěji strukturované binární soubory

Tím, že metody array.tofile a array.fromfile vyžadují předání handle souboru, jenž musí být otevřený pro zápis resp. pro čtení (a ideálně navíc v binárním režimu), je umožněna více či méně sofistikovaná práce se složitěji strukturovanými binárními soubory. V takových souborech můžeme kombinovat různé sekce, využít nástroj pickle apod. V dalším příkladu je ukázána tvorba binárního souboru, který obsahuje pět částí:

  1. Hlavička o délce sedmi bajtů
  2. Pole se 100 prvky typu bajt (celkem 100 bajtů)
  3. Rozdělovací prvek o délce deseti bajtů
  4. Pole se 100 prvky typu short (celkem 200 bajtů)
  5. Patička o délce tří bajtů

Celková velikost výsledného binárního souboru by měla být:

7 + 100 + 10 + 200 + 3 = 320 bajtů

Realizace zápisu takového souboru může vypadat následovně:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from array import array
 
# prázdné struktury
a1 = array("B")
a2 = array("H")
 
# naplnit daty
for i in range(100):
    a1.append(i)
    a2.append(i)
 
 
with open("structured.bin", "wb") as f:
    f.write(b"HEADER\n")
    a1.tofile(f)
    f.write(b"\nsplitter\n")
    a2.tofile(f)
    f.write(b"END")

Po spuštění skriptu zjistíme celkovou velikost souboru:

$ ls -l structured.bin 
 
-rw-rw-r-- 1 ptisnovs ptisnovs 320 Jan 12 12:25 structured.bin

Skutečně má délku očekávaných 320 bajtů.

Obsah souboru, umístění hlavičky atd. nám prozradí nástroj hd:

$ hd structured.bin 
 
00000000  48 45 41 44 45 52 0a 00  01 02 03 04 05 06 07 08  |HEADER..........|
00000010  09 0a 0b 0c 0d 0e 0f 10  11 12 13 14 15 16 17 18  |................|
00000020  19 1a 1b 1c 1d 1e 1f 20  21 22 23 24 25 26 27 28  |....... !"#$%&'(|
00000030  29 2a 2b 2c 2d 2e 2f 30  31 32 33 34 35 36 37 38  |)*+,-./012345678|
00000040  39 3a 3b 3c 3d 3e 3f 40  41 42 43 44 45 46 47 48  |9:;<=>?@ABCDEFGH|
00000050  49 4a 4b 4c 4d 4e 4f 50  51 52 53 54 55 56 57 58  |IJKLMNOPQRSTUVWX|
00000060  59 5a 5b 5c 5d 5e 5f 60  61 62 63 0a 73 70 6c 69  |YZ[\]^_`abc.spli|
00000070  74 74 65 72 0a 00 00 01  00 02 00 03 00 04 00 05  |tter............|
00000080  00 06 00 07 00 08 00 09  00 0a 00 0b 00 0c 00 0d  |................|
00000090  00 0e 00 0f 00 10 00 11  00 12 00 13 00 14 00 15  |................|
000000a0  00 16 00 17 00 18 00 19  00 1a 00 1b 00 1c 00 1d  |................|
000000b0  00 1e 00 1f 00 20 00 21  00 22 00 23 00 24 00 25  |..... .!.".#.$.%|
000000c0  00 26 00 27 00 28 00 29  00 2a 00 2b 00 2c 00 2d  |.&.'.(.).*.+.,.-|
000000d0  00 2e 00 2f 00 30 00 31  00 32 00 33 00 34 00 35  |.../.0.1.2.3.4.5|
000000e0  00 36 00 37 00 38 00 39  00 3a 00 3b 00 3c 00 3d  |.6.7.8.9.:.;.<.=|
000000f0  00 3e 00 3f 00 40 00 41  00 42 00 43 00 44 00 45  |.>[email protected]|
00000100  00 46 00 47 00 48 00 49  00 4a 00 4b 00 4c 00 4d  |.F.G.H.I.J.K.L.M|
00000110  00 4e 00 4f 00 50 00 51  00 52 00 53 00 54 00 55  |.N.O.P.Q.R.S.T.U|
00000120  00 56 00 57 00 58 00 59  00 5a 00 5b 00 5c 00 5d  |.V.W.X.Y.Z.[.\.]|
00000130  00 5e 00 5f 00 60 00 61  00 62 00 63 00 45 4e 44  |.^._.`.a.b.c.END|
00000140

14. Teoretická a praktická rychlost přístupu k prvkům polí

V tomto článku jsme již na několika místech porovnávali klasický seznam s poli realizovanými ve standardním balíčku array. Vzhledem k tomu, že prvky pole jsou v této datové struktuře uloženy přímo, tj. bez výplní a boxingu, je spotřeba operační paměti polem menší, než je tomu v případě seznamů. To je vlastně jediná výhoda této datové struktury. Samotné čtení hodnoty prvky či zápis hodnoty prvku je ve skutečnosti časově náročnější, protože je nutné provádět boxing při čtení a unboxing při zápisu (to jsou operace, které obalí hodnotu „objektem“, protože v Pythonu se se všemi hodnotami pracuje jako s objekty).

Větší časovou náročnost čtení a zápisu prvků pole si můžeme ověřit na libovolném algoritmu, který s prvky pole či seznamu intenzivně pracuje. Pro měření a jako jednoduchý benchmark budeme realizovat již několikrát zmíněný algoritmus bublinkového řazení (skutečně se v češtině takto označuje). Ten lze pochopitelně realizovat nad seznamy, a to velmi snadno:

from time import perf_counter
import random
 
size = 10000
 
a = [random.randrange(0, 10000) for i in range(size)]
 
t1 = perf_counter()
 
for i in range(size - 1, 0, -1):
    for j in range(0, i):
        if a[j] > a[j + 1]:
            a[j], a[j + 1] = a[j + 1], a[j]
 
t2 = perf_counter()
 
print(f"Sorted in {t2-t1} seconds:")
 
print("\nBegins with:")
print(a[:100])
 
print("\nEnds with:")
print(a[-100:])

15. Tentýž výpočet, ovšem realizovaný s využitím polí

Zápis algoritmu z předchozí kapitoly je možné velmi jednoduše upravit do takové podoby, aby se namísto seznamů požívalo pole. Použijeme pole celých čísel typu integer bez znaménka, které mají minimální rozsah hodnot 0..65535. Povšimněte si, že samotná výkonná část algoritmu (dvojice vnořených smyček a podmínka) zůstala totožná, to díky tomu, že pro seznamy i pro pole se používá stejný zápis operátoru indexování:

from time import perf_counter
from array import array
import random
 
size = 10000
 
a = array("I", [random.randrange(0, 10000) for i in range(size)])
 
t1 = perf_counter()
 
for i in range(size - 1, 0, -1):
    for j in range(0, i):
        if a[j] > a[j + 1]:
            a[j], a[j + 1] = a[j + 1], a[j]
 
t2 = perf_counter()
 
print(f"Sorted in {t2-t1} seconds:")
 
print("\nBegins with:")
print(a[:100])
 
print("\nEnds with:")
print(a[-100:])

Nyní začíná nejzajímavější část – porovnání rychlosti běhu obou implementací:

Implementace Čas běhu
seznam 10,3 s
pole 12,5 s

Z výsledků je patrné, že – i když to je neintuitivní – je čtení a zápis prvků do seznamů rychlejší, než provedení týchž operací s polem (primitivních) hodnot. Opět tedy zopakujme – pole array jsou výhodná pouze co se týče ušetření obsazení operační paměti, nikoli z důvodu větší rychlosti.

Poznámka: totéž platí i pro nd-array z knihovny NumPy v případě, že se operace provádí přímo v Pythonu a nikoli v NumPy (příkladem je výpočet sumy – buď programově smyčkou, nebo zavoláním array.sum()).

16. Interní způsob uložení pole v paměti

Na závěr se na chvíli podívejme pod kapotu Pythonu, konkrétně na to, jak vlastně vypadá interní způsob uložení pole v operační paměti. Příslušné zdrojový kódy je možné nalézt v souboru Modules/arraymodule.c.

První datovou strukturou, o které se zmíníme, je struktura nazvaná arraydescr. Ta slouží pro popis jednotlivých typů pole resp. typů prvků, které jsou do zkonstruovaného pole vkládány:

struct arraydescr {
    char typecode;
    int itemsize;
    PyObject * (*getitem)(struct arrayobject *, Py_ssize_t);
    int (*setitem)(struct arrayobject *, Py_ssize_t, PyObject *);
    int (*compareitems)(const void *, const void *, Py_ssize_t);
    const char *formats;
    int is_integer_type;
    int is_signed;
};

Popis všech podporovaných typů polí nalezneme v poli nazvaném descriptors:

static const struct arraydescr descriptors[] = {
    {'b', 1, b_getitem, b_setitem, b_compareitems, "b", 1, 1},
    {'B', 1, BB_getitem, BB_setitem, BB_compareitems, "B", 1, 0},
    {'u', sizeof(wchar_t), u_getitem, u_setitem, u_compareitems, "u", 0, 0},
    {'h', sizeof(short), h_getitem, h_setitem, h_compareitems, "h", 1, 1},
    {'H', sizeof(short), HH_getitem, HH_setitem, HH_compareitems, "H", 1, 0},
    {'i', sizeof(int), i_getitem, i_setitem, i_compareitems, "i", 1, 1},
    {'I', sizeof(int), II_getitem, II_setitem, II_compareitems, "I", 1, 0},
    {'l', sizeof(long), l_getitem, l_setitem, l_compareitems, "l", 1, 1},
    {'L', sizeof(long), LL_getitem, LL_setitem, LL_compareitems, "L", 1, 0},
    {'q', sizeof(long long), q_getitem, q_setitem, q_compareitems, "q", 1, 1},
    {'Q', sizeof(long long), QQ_getitem, QQ_setitem, QQ_compareitems, "Q", 1, 0},
    {'f', sizeof(float), f_getitem, f_setitem, NULL, "f", 0, 0},
    {'d', sizeof(double), d_getitem, d_setitem, NULL, "d", 0, 0},
    {'\0', 0, 0, 0, 0, 0, 0} /* Sentinel */
};

Samotná datová struktura představující pole obsahuje především standardní hlavičku všech proměnných spravovaných Pythonem (PyObject_VAR_HEAD), dále ukazatel na céčkovské pole ob_item (to se pochopitelně nejprve musí alokovat), objem alokované paměti allocated a jeden z výše uvedených popisků pole ob_descr:

typedef struct arrayobject {
    PyObject_VAR_HEAD
    char *ob_item;
    Py_ssize_t allocated;
    const struct arraydescr *ob_descr;
    PyObject *weakreflist; /* List of weak references */
    Py_ssize_t ob_exports;  /* Number of exported buffers */
} arrayobject;

Pro úplnost si ještě uveďme funkci, která zajistí vytvoření pole zadaného typu a velikosti. Jedná se o poměrně přímočarý kód:

static PyObject *
newarrayobject(PyTypeObject *type, Py_ssize_t size, const struct arraydescr *descr)
{
    arrayobject *op;
    size_t nbytes;
 
    if (size < 0) {
        PyErr_BadInternalCall();
        return NULL;
    }
 
    /* Check for overflow */
    if (size > PY_SSIZE_T_MAX / descr->itemsize) {
        return PyErr_NoMemory();
    }
    nbytes = size * descr->itemsize;
    op = (arrayobject *) type->tp_alloc(type, 0);
    if (op == NULL) {
        return NULL;
    }
    op->ob_descr = descr;
    op->allocated = size;
    op->weakreflist = NULL;
    Py_SET_SIZE(op, size);
    if (size <= 0) {
        op->ob_item = NULL;
    }
    else {
        op->ob_item = PyMem_NEW(char, nbytes);
        if (op->ob_item == NULL) {
            Py_DECREF(op);
            return PyErr_NoMemory();
        }
    }
    op->ob_exports = 0;
    return (PyObject *) op;
}
Poznámka: povšimněte si způsobu výpočtu alokované paměti.

17. Algoritmus pro alokaci paměti pro zvětšující a zmenšující se pole

Vzhledem k tomu, že do polí lze přidávat nové prvky, je interně nutné provádět realokaci paměti, což je pochopitelně náročná operace, protože mj. vyžaduje kopii prvků z původního bloku paměti do bloku nového. Z tohoto důvodu je vhodné zvolit takovou strategii, aby se pole realokovalo s relativně malou frekvencí (tedy zcela jistě ne pro každý nový prvek), ovšem na druhé straně nechceme, aby pole zabíralo zbytečně velký blok paměti. Řešení tohoto problému není nijak nové – pokud již kapacita pole nedostačuje, provede se realokace a přitom se naalokuje více paměti. O jak velký kus se jedná je odvozeno z aktuálního počtu prvků. U velkého pole se předpokládá, že poroste ještě více, takže se rezervuje větší blok paměti.

Takto celý algoritmus pro změnu velikosti pole (a tedy i pro realokace v některých okamžicích) vypadá:

static int
array_resize(arrayobject *self, Py_ssize_t newsize)
{
    char *items;
    size_t _new_size;
 
    if (self->ob_exports > 0 && newsize != Py_SIZE(self)) {
        PyErr_SetString(PyExc_BufferError,
            "cannot resize an array that is exporting buffers");
        return -1;
    }
 
    /* Bypass realloc() when a previous overallocation is large enough
       to accommodate the newsize.  If the newsize is 16 smaller than the
       current size, then proceed with the realloc() to shrink the array.
    */
 
    if (self->allocated >= newsize &&
        Py_SIZE(self) < newsize + 16 &&
        self->ob_item != NULL) {
        Py_SET_SIZE(self, newsize);
        return 0;
    }
 
    if (newsize == 0) {
        PyMem_Free(self->ob_item);
        self->ob_item = NULL;
        Py_SET_SIZE(self, 0);
        self->allocated = 0;
        return 0;
    }
 
    /* This over-allocates proportional to the array size, making room
     * for additional growth.  The over-allocation is mild, but is
     * enough to give linear-time amortized behavior over a long
     * sequence of appends() in the presence of a poorly-performing
     * system realloc().
     * The growth pattern is:  0, 4, 8, 16, 25, 34, 46, 56, 67, 79, ...
     * Note, the pattern starts out the same as for lists but then
     * grows at a smaller rate so that larger arrays only overallocate
     * by about 1/16th -- this is done because arrays are presumed to be more
     * memory critical.
     */
 
    _new_size = (newsize >> 4) + (Py_SIZE(self) < 8 ? 3 : 7) + newsize;
    items = self->ob_item;
    /* XXX The following multiplication and division does not optimize away
       like it does for lists since the size is not known at compile time */
    if (_new_size <= ((~(size_t)0) / self->ob_descr->itemsize))
        PyMem_RESIZE(items, char, (_new_size * self->ob_descr->itemsize));
    else
        items = NULL;
    if (items == NULL) {
        PyErr_NoMemory();
        return -1;
    }
    self->ob_item = items;
    Py_SET_SIZE(self, newsize);
    self->allocated = _new_size;
    return 0;
}

18. Průběh alokace

Průběh alokace můžeme sledovat například tímto jednoduchým skriptem. Ten je opět založen na modulu pympler. Do pole bajtů se postupně přidávají jednotlivé prvky a zjišťuje se, ve kterém okamžiku dojde ke zvětšení pole. Informace o tomto zvětšení se následně vypíše na terminál:

#!/usr/bin/env python3
# vim: set fileencoding=utf-8
 
from array import array
from sys import getsizeof
 
from pympler.asizeof import asizeof
 
# prázdná struktura
a1 = array("B")
 
last_size = asizeof(a1)
last_index = 0
 
# naplnit daty
for i in range(1000):
    a1.append(i%100)
    size = asizeof(a1)
    if size != last_size:
        print(f"{i:5}  {size:5}  {size-last_size:+5}   {last_index:4}-{i:<4}")
        last_index = i
        last_size = size

Skript spustíme:

prace_s_linuxem_tip

$ python3 alloc_details.py

Zajímavé je sledovat třetí sloupec ukazující, o kolik bajtů se pole zvětší při přidání n-tého prvku (jeho index je v prvním sloupci). Zde je nutné upozornit na problém sledování využití paměti – do hry totiž vstupuje jak vlastní realokace, tak i výpočet zarovnání atd. Z tohoto důvodu počty „nesedí“ s očekávaným chováním, tedy že ve třetím sloupci bude monotonně rostoucí řada. Nicméně trend je zcela jasně patrný:

    0     80     +8      0-0
    8     88     +8      0-8
   16    104    +16      8-16
   25    112     +8     16-25
   34    120     +8     25-34
   44    128     +8     34-44
   54    144    +16     44-54
   65    152     +8     54-65
   77    168    +16     65-77
   89    176     +8     77-89
  102    192    +16     89-102
  116    208    +16    102-116
  131    224    +16    116-131
  147    240    +16    131-147
  164    256    +16    147-164
  182    280    +24    164-182
  201    296    +16    182-201
  221    320    +24    201-221
  242    344    +24    221-242
  265    368    +24    242-265
  289    392    +24    265-289
  315    416    +24    289-315
  342    448    +32    315-342
  371    480    +32    342-371
  402    512    +32    371-402
  435    544    +32    402-435
  470    584    +40    435-470
  507    624    +40    470-507
  546    664    +40    507-546
  588    704    +40    546-588
  632    752    +48    588-632
  679    808    +56    632-679
  729    856    +48    679-729
  782    912    +56    729-782
  838    976    +64    782-838
  898   1040    +64    838-898
  962   1104    +64    898-962

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

Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python 3 byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. Odkazy na jednotlivé příklady jsou uvedeny v následující tabulce:

# Demonstrační příklad Stručný popis příkladu Cesta
1 empty_structs.py inicializace prázdných struktur (seznamu a polí) a získání jejich velikosti https://github.com/tisnik/most-popular-python-libs/blob/master/array/em­pty_structs.py
2 hundred_items.py přidání 100 prvků do každé struktury a získání jejich velikosti https://github.com/tisnik/most-popular-python-libs/blob/master/array/hun­dred_items.py
3 10000_items.py přidání 10000 prvků do každé struktury a získání jejich velikosti https://github.com/tisnik/most-popular-python-libs/blob/master/array/10000_items.py
4 read_values.py čtení hodnot prvků ze seznamů a polí https://github.com/tisnik/most-popular-python-libs/blob/master/array/read_values.py
5 insert_item.py operace pro vložení prvku na zvolený index https://github.com/tisnik/most-popular-python-libs/blob/master/array/insert_item.py
6 tobytes.py konverze polí na hodnotu typu bytes s jejím tiskem https://github.com/tisnik/most-popular-python-libs/blob/master/array/tobytes.py
7 export_to_file.py export polí do binárního souboru https://github.com/tisnik/most-popular-python-libs/blob/master/array/ex­port_to_file.py
8 import_from_file.py import polí z binárního souboru https://github.com/tisnik/most-popular-python-libs/blob/master/array/im­port_from_file.py
9 bubble_sort1.py algoritmus bublinkového řazení realizovaný nad seznamy https://github.com/tisnik/most-popular-python-libs/blob/master/array/bub­ble_sort1.py
10 bubble_sort2.py algoritmus bublinkového řazení realizovaný nad polem https://github.com/tisnik/most-popular-python-libs/blob/master/array/bub­ble_sort2.py
11 structured_file.py zápis polí do souboru se strukturou https://github.com/tisnik/most-popular-python-libs/blob/master/array/struc­tured_file.py

20. Odkazy na Internetu

  1. array — Efficient arrays of numeric values
    https://docs.python.org/3/li­brary/array.html
  2. Top 5 Python Memory Profilers
    https://stackify.com/top-5-python-memory-profilers/
  3. Pympler na GitHubu
    https://github.com/pympler/pympler
  4. Pympler na PyPI
    https://pypi.org/project/Pympler/
  5. Dokumentace k balíčku Pympler
    https://pympler.readthedoc­s.io/en/latest/
  6. Guppy 3 na GitHubu
    https://github.com/zhuyife­i1999/guppy3/
  7. Guppy 3 na PyPI
    https://pypi.org/project/guppy3/
  8. Memory Profiler na GitHubu
    https://github.com/python­profilers/memory_profiler
  9. Memory Profiler na PyPI
    https://pypi.org/project/memory-profiler/
  10. How to use guppy/heapy for tracking down memory usage
    https://smira.ru/wp-content/uploads/2011/08/heapy.html
  11. Identifying memory leaks
    https://pympler.readthedoc­s.io/en/latest/muppy.html#mup­py
  12. How do I determine the size of an object in Python?
    https://stackoverflow.com/qu­estions/449560/how-do-i-determine-the-size-of-an-object-in-python
  13. Why is bool a subclass of int?
    https://stackoverflow.com/qu­estions/8169001/why-is-bool-a-subclass-of-int
  14. Memory Management in Python
    https://realpython.com/python-memory-management/
  15. Why do ints require three times as much memory in Python?
    https://stackoverflow.com/qu­estions/23016610/why-do-ints-require-three-times-as-much-memory-in-python
  16. cpython/Include/cpython/longintrepr.h
    https://github.com/python/cpyt­hon/blob/main/Include/cpyt­hon/longintrepr.h#L64
  17. sys — System-specific parameters and functions
    https://docs.python.org/3/li­brary/sys.html
  18. Stránky projektu Numpy
    https://numpy.org/
  19. Numpy na GitHubu
    https://github.com/numpy/numpy
  20. NumPy documentation
    https://numpy.org/doc/stable/
  21. The N-dimensional array (ndarray)
    https://numpy.org/doc/sta­ble/reference/arrays.ndarra­y.html
  22. numpy.ndarray
    https://numpy.org/doc/sta­ble/reference/generated/num­py.ndarray.html
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 »