Hlavní navigace

Kontejnery v Pythonu: zdaleka nejde jen o n-tice, seznamy, množiny a slovníky

30. 4. 2024
Doba čtení: 34 minut

Sdílet

 Autor: Root.cz s využitím DALL-E
Seznámíme se s vybranými kontejnery (containers), které je možné použít při tvorbě aplikací v Pythonu. Kontejnerů přitom existuje velké množství a zdaleka se nejedná jen o n-tice, seznamy, množiny a slovníky.

Obsah

1. Kontejnery v Pythonu: zdaleka nejde jen o n-tice, seznamy, množiny a slovníky

2. Kontejnery v kontextu programovacího jazyka Python

3. n-tice

4. Seznamy

5. Standardní množiny a jejich varianty

6. Standardní slovníky a jejich varianty

7. Obousměrné fronty

8. Přidání prvku do fronty zleva či zprava, vložení prvku do libovolného místa fronty

9. Odstranění nejlevějšího či nejpravějšího prvku z fronty

10. Metody reverse a rotate

11. Multimnožiny (multiset)

12. Multislovníky (multidict)

13. Balíček python-box zajišťující výběr hodnot s využitím klíčů nebo atributů

14. Instalace balíčku python-box

15. Datová struktura Box je kontejnerem

16. Příklady využití datové struktury Box

17. Praktická ukázka: získání hodnot z několikanásobně vnořené datové struktury načtené z JSONu

18. Klíč složený z několika selektorů

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

20. Odkazy na Internetu

1. Kontejnery v Pythonu: zdaleka nejde jen o n-tice, seznamy, množiny a slovníky

V dnešním článku se seznámíme s vybranými kontejnery (containers), které je možné použít při tvorbě aplikací v programovacím jazyku Python. Kontejnerů přitom existuje velké množství. Kromě notoricky známé čtveřice n-tice (tuple), seznam (list), množina (set) a slovník (dict) existují i ve standardní knihovně další typy kontejnerů a jiné lze doinstalovat (a pravděpodobně již některé z nich máte nainstalovány jako tranzitivní závislost jiné knihovny, například knihovny requests apod.).

V poměrně velkém množství zdrojových kódů, které mi chodí na review, se přitom používají kontejnery, jejichž vlastnosti přesně neodpovídají potřebám řešeného problému. To vede k tomu, že je nutné explicitně znovu a znovu provádět operace, jež by v případě použití odlišného (vhodnějšího) typu kontejneru byly vyřešeny „zadarmo“, protože je zajistí samotný kontejner. Asi nejtypičtějším příkladem jsou algoritmy, v nichž se používají unikátní hodnoty. V takovém případě může být výhodné použít množiny (set) a nikoli z nějakého důvodu často nasazované seznamy (list). Mezi další dva příklady, s nimž se často setkávám, patří snaha o realizaci kontejneru, který je znám pod označením multidict a taktéž explicitní programování chování kontejneru Counter. V některých případech je taktéž nutné pracovat s oboustranným mapováním (jednostranné mapování zajišťují slovníky).

V dnešním článku se nejprve ve stručnosti seznámíme s těmi kontejnery, které jsou součástí standardní knihovny Pythonu – a nejedná se v žádném případě pouze o známou čtveřici n-tice, seznam, množina a slovník. Posléze si ovšem popíšeme i některé další typy kontejnerů realizované v modulech (balíčcích), které je nutné doinstalovat. A na konec si ukážeme základní vlastnosti modulu nazvaného Box (či python-box), který umožňuje namísto klasických selektorů (u slovníků) použít tečkovou notaci tak, jakoby prvky byly uloženy formou atributů. Ostatně tento koncept (řekněme dualitu mezi selektorem a jménem atributu) není nijak nový a vidět jsme ho mohli například v programovacím jazyku Lua, v němž je použit při práci s kontejnerem nazvaným tabulka (table), což je kombinace pole a slovníku.

2. Kontejnery v kontextu programovacího jazyka Python

Na tomto místě je možná vhodné si ve stručnosti připomenout, co vlastně v kontextu programovacího jazyka Python znamená označení kontejner. Jedná se o datovou strukturu, kterou lze využít k uložení dalších hodnot s tím, že jsou většinou zajištěny některé další vlastnosti, jež se mohou lišit podle typu kontejneru. Například u seznamů je zaručeno pořadí uložených hodnot, u množin pak unikátnost hodnot atd. Existuje ale ještě striktnější definice kontejneru, která je ovšem platná právě pouze v kontextu jazyka Python. Kontejner (tedy container) je taková třída, která implementuje metodu __contains__. Z tohoto pohledu do této kategorie skutečně patří všechny čtyři základní kontejnery, o čemž se ostatně můžeme velmi snadno přesvědčit přímo v interaktivní smyčce interpretru programovacího jazyka Python výpisem metod a atributů tříd list, tuple, set a dict:

$ python3
 
Python 3.11.8 (main, Feb  7 2024, 00:00:00) [GCC 13.2.1 20231011 (Red Hat 13.2.1-4)] on linux
Type "help", "copyright", "credits" or "license" for more information.
 
>>> dir(list)
['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__',
'__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__',
'__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__',
'__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__',
'__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear',
'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse',
'sort']
 
>>> dir(tuple)
['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__getitem__', '__getnewargs__', '__getstate__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__',
'__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'count', 'index']
 
>>> dir(set)
['__and__', '__class__', '__class_getitem__', '__contains__', '__delattr__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__getstate__', '__gt__', '__hash__', '__iand__', '__init__',
'__init_subclass__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__',
'__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__',
'__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__',
'__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add',
'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection',
'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove',
'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
 
>>> dir(dict)
['__class__', '__class_getitem__', '__contains__', '__delattr__',
'__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__ior__', '__iter__', '__le__', '__len__',
'__lt__', '__ne__', '__new__', '__or__', '__reduce__', '__reduce_ex__',
'__repr__', '__reversed__', '__ror__', '__setattr__', '__setitem__',
'__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys',
'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']

3. n-tice

Základním a současně i velmi důležitým a často používaným kontejnerem jsou v Pythonu n-tice. Na rozdíl od dále zmíněných seznamů jsou n-tice neměnitelné (immutable) se všemi z toho plynoucími důsledky – obecně platí, že n-tice lze použít jako klíče slovníků, lze je bez problémů použít pro specifikaci výchozí hodnoty parametru funkce/metody (na rozdíl od slovníků) atd.

Při konstrukci n-tice je nutné si pouze dát pozor na to, jak zapisovat jednoprvkovou n-tici, protože samotné kulaté závorky mají v Pythonu hned několik významů:

# tříprvková n-tice
t1 = (1, 2, 3)
 
# jednoprvková n-tice
t2 = (4,)
 
# pozor - není tuple!
t3 = (5)
 
# nehomogenní n-tice
t4 = (4, 3.14, "string", [])
 
# prázdná n-tice
t5 = ()
 
print(t1)
print(t2)
print(t3)
print(t4)
print(t5)

n-tice lze spojovat operátorem +, který v tomto případě pochopitelně není komutativní:

x = (1, 2, 3)
y = (3, 4, 5)
 
print(x + y)
print(y + x)

Taktéž můžeme použít operátor * pro vytvoření nové n-tice, ve které se několikrát opakuje n-tice původní:

x = (1, 2, 3)
y = x * 3
 
print(x)
print(y)
Poznámka: pokud n-tici opakujeme nulakrát, bude výsledkem prázdná n-tice.

A u n-tic lze použít operátor in, podobně jako u všech dalších typů kontejnerů:

x = (1, 2, 3)
y = (3, 4, 5)
z = x + y
 
print(1 in z)
print(10 in z)
print("foobar" in z)

U n-tic vzniká ještě jeden drobný syntaktický problém. Očekávali bychom, že Python jako do jisté míry ortogonální programovací jazyk, bude podporovat generátorovou notaci i pro n-tice. Jenže jak je vlastně možné takový výraz zapsat, když samotné kulaté závorky jsou použity přímo pro generátorovou notaci a ostatní typy závorek jsou již „obsazeny“ pro seznamy, množiny a slovníky? Teoreticky by sice bylo možné použít úhlové závorky < a >, ovšem tyto znaky se v Pythonu pro zápis závorek nepoužívají, což je asi z hlediska celkové čitelnosti jen dobře.

Nebo lze alternativně použít následující trik – zapsat hvězdičku na začátek výrazu a čárku na jeho konec. Jedná se o zápis operace rozbalení (unpack) popsanou v PEP-448 a zavedenou v Pythonu 3.5. Zápis výrazu, jehož výsledkem je skutečně n-tice, tedy může vypadat takto (nesmíme ovšem zapomenout na čárku na konci):

t = *(x*2 for x in range(11) if x%3 != 0),
 
print(t)

Výsledkem v tomto případě bude:

(2, 4, 8, 10, 14, 16, 20)

4. Seznamy

Pravděpodobně nejčastěji používaným kontejnerem v Pythonu jsou seznamy (list). Ty jsou na rozdíl od n-tic měnitelné (mutable), což při některých operacích může programátora, který s Pythonem začíná, překvapit. Nejznámější problém způsobuje specifikace výchozí hodnoty nepovinného parametru funkce:

>>> def foo(l=[]):
...     l.append(".")
...     print(l)
...
 
>>> foo()
['.']
 
>>> foo()
['.', '.']
 
>>> foo()
['.', '.', '.']

Prvky seznamů se vybírají přes celočíselné indexy, přičemž záporné hodnoty lze použít pro přístup od konce seznamu. Mazání prvků zajišťuje příkaz (ano, příkaz, ne metoda), del:

seznam = [1, 2, 3, 4]
 
print(seznam[0])
print(seznam[1])
print(seznam[-1])
print(seznam[-2])
 
seznam.append(5)
seznam.append(6)
 
seznam.insert(0, -10)
seznam.insert(0, -100)
 
print(seznam)
 
del seznam[0]
print(seznam)
del seznam[-1]
print(seznam)

I seznamy lze spojovat nekomutativním operátorem +, stejně jako n-tice:

seznam1 = [1, 2, 3]
seznam2 = [4, 5, 6]
 
seznam3 = seznam1 + seznam2
 
print(seznam3)

Vytvoření nového seznamu získaného opakováním vstupního seznam pomocí nekomutativního operátoru *:

seznam1 = [1, 2, 3]
 
seznam2 = seznam1 * 3
print(seznam2)

Řazení prvků v seznamu je další operací, která může začínající programátory v Pythonu překvapit. K dispozici je totiž metoda sort, která prvky řadí in-situ (mění seznam) a funkce sorted, která naopak vrací nový seřazený seznam:

seznam = [5, 4, 1, 3, 4, 100, -1]
print(seznam)
 
seznam.sort()
print(seznam)
 
seznam = [5, 4, 1, 3, 4, 100, -1]
print(seznam)
 
seznam2 = sorted(seznam)
print(seznam)
print(seznam2)

Prvky seznamu lze v případě potřeby otočit metodou reverse, která taktéž pracuje in-situ (mění seznam):

seznam = [5, 4, 1, 3, 4, 100, -1]
print(seznam)
 
seznam.reverse()
print(seznam)

A konečně se podívejme na takzvanou generátorovou notaci, která umožňuje zápis „funkcionálních“ operací typu map a filter idiomatickým způsobem (pro Python):

seznam = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
 
seznam2 = [item * 2 for item in seznam]
 
seznam3 = [item for item in seznam if item % 3 == 0]
 
print(seznam)
print(seznam2)
print(seznam3)

5. Standardní množiny a jejich varianty

Seznamy zmíněné v předchozí kapitole jsou mnohdy využívány i v těch částech programového kódu, v nichž by bylo výhodnější použít spíše množiny (set). U klasických množin je zaručena unikátnost prvků a jedná se o měnitelnou datovou strukturu, pro kterou platí podobná pravidla a určitá omezení, jako pro seznamy. Nejprve se podívejme na konstrukci množin, přičemž pozor je nutné dát především na prázdnou množinu, kterou je nutné zkonstruovat zavoláním set a nikoli zápisem prázdných složených závorek (to by vznikl slovník). Taktéž si ukážeme modifikaci množin metodami add a update:

s = {1, 2, 3, 4}
print(s)
 
s2 = {"hello", "world", "!", 0}
print(s2)
 
s3 = set()
print(s3)
 
s3.add(1)
s3.add(2)
print(s3)
 
s3.update([3, 4, 5])
print(s3)

Podporovány jsou všechny standardní množinové operace zapisované operátory | (sjednocení), & (průnik), – (diference) a ^ (symetrická diference). A množiny jsou kontejnery, takže podporují operátor in pro test na existenci prvku:

s1 = {1, 2, 3, 4}
s2 = {3, 4, 5, 6}
 
print(s1)
print(s2)
 
print(s1 | s2)
print(s1 & s2)
print(s1 - s2)
print(s2 - s1)
print(s1 ^ s2)
 
print(1 in s1)
print(10 in s1)

Množinové operace představují silný výrazový prostředek, který dokáže nahradit explicitně zapsané smyčky či generátorovou notaci. Patrné je to z následujícího příkladu získaného z dokumentace:

engineers = {"John", "Jane", "Jack", "Janice"}
programmers = {"Jack", "Sam", "Susan", "Janice"}
managers = {"Jane", "Jack", "Susan", "Zack"}
 
employees = engineers | programmers | managers
engineering_management = engineers & managers
fulltime_management = managers - engineers - programmers
 
print(engineers)
print(programmers)
print(managers)
print(employees)
print(engineering_management)
print(fulltime_management)

Výsledky množinových operací, které posloužily pro zjištění různých skupin zaměstnanců, přičemž se tyto skupiny prolínají:

{'Janice', 'Jack', 'John', 'Jane'}
{'Janice', 'Jack', 'Susan', 'Sam'}
{'Zack', 'Jack', 'Susan', 'Jane'}
{'Jack', 'John', 'Janice', 'Zack', 'Susan', 'Sam', 'Jane'}
{'Jack', 'Jane'}
{'Zack'}

Odstranění prvků z množiny lze provést metodami discard a remove. Tyto metody se od sebe liší testem (či absencí testu) na existenci prvku. Metoda discard pro neexistující prvek neprovede žádnou operaci zatímco metoda remove v takovém případě vyhodí výjimku:

s1 = {1, 2, 3, 4}
print(s1)
 
s1.discard(2)
print(s1)
 
s1.discard(1000)
print(s1)
 
s1.remove(3)
print(s1)
 
s1.remove(1000)
print(s1)

Na závěr se podívejme na zápis generátorové notace množin. Zapisuje se prakticky stejně, jako v případě seznamů, ovšem závorky okolo výrazu jsou složené a ne hranaté:

s = {x*2 for x in range(11)}
 
print(s)

Alternativou k typu set je frozenset, což je neměnitelná varianta množin.

6. Standardní slovníky a jejich varianty

Čtvrtým základním kontejnerem v programovacím jazyku Python jsou slovníky (dict). Opět se jedná o měnitelné datové struktury, v nichž jsou hodnoty vybírány na základě klíče a nikoli indexu. Každý prvek uložený ve slovníku se tedy skládá z dvojice klíč+hodnota, přičemž klíč je unikátní (později se ovšem setkáme s multislovníky, kde tomu tak není).

Konstrukci slovníku a výběr prvků s využitím klíče asi není zapotřebí podrobně popisovat:

d = {"id": 1, "name": "Eda", "surname": "Wasserfall"}
 
print(d)
 
print(d["name"])
 
d["hra"] = "Svestka"
 
print(d)

Vymazání prvku ze slovníku je provedeno příkazem del:

d = {"id": 1, "name": "Eda", "surname": "Wasserfall"}
 
print(d)
 
print(d["name"])
 
d["hra"] = "Svestka"
 
print(d)
 
del d["id"]
 
print(d)

V případě, že prvek s daným klíčem neexistuje, je při pokusu o jeho čtení či vymazání vyhozena výjimka KeyError:

d = {"id": 1, "name": "Eda", "surname": "Wasserfall"}
 
del d["id"]
del d["foo"]
del d["bar"]
 
print(d)

U všech předchozích kontejnerů existovala nějaká operace pro jejich spojení. U slovníků tomu tak není, takže si musíme pomoci operací unpack, což ovšem není příliš čitelné a mohou nastat problémy v případě vícenásobného použití stejných klíčů:

d1 = {"id": 1, "name": "Eda", "surname": "Wasserfall"}
d2 = {"foo": "F", "bar": "B", "baz": "Z"}
 
d = {**d1, **d2}
 
print(d)

Generátorová notace slovníku se zapisuje následujícím způsobem:

d = {x: x*2 for x in range(11) if x%3 != 0}
 
print(d)

Ke standardním slovníkům (které se navíc mohou chovat v různých verzích Pythonu odlišně) existuje velké množství variant s různými vlastnostmi, včetně multislovníků a obousměrných mapování. Některé z těchto variant si popíšeme příště.

7. Obousměrné fronty

Dalším „klasickým“ kontejnerem podporovaným ve standardní knihovně Pythonu, o němž se v dnešním článku zmíníme, je kontejner nazvaný deque, což je jedna z možných implementací obousměrné fronty (double ended queue). Jedná se tedy o kontejner, který podporuje operace připojení nového prvku k oběma koncům fronty, popř. naopak k získání (a odstranění) prvku z libovolného konce. Podporovány jsou ovšem navíc i dvě operace, které typicky u implementací obousměrných front nenajdeme. Jedná se o operaci určenou pro rotaci prvků uložených ve frontě a taktéž o operaci, která vede k otočení fronty, tj. ke změně pořadí všech prvků, které jsou ve frontě uloženy.

Poznámka: obousměrnou frontu je možné pochopitelně použít i ve funkci zásobníku (stack), zde konkrétně persistentního zásobníku. Z tohoto důvodu v Pyrsistent nenalezneme přímo kontejner stack, resp. pstack.

O tom, jestli je obousměrná fronta plnohodnotným kontejnerem, se přesvědčíme stejným způsobem, jako u standardních kontejnerů – zjištěním zda implementuje metodu __contains__:

>>> from collections import deque
 
>>> dir(deque)
['__add__', '__class__', '__class_getitem__', '__contains__', '__copy__',
'__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__getitem__', '__getstate__', '__gt__',
'__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__',
'__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__',
'__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__',
'append', 'appendleft', 'clear', 'copy', 'count', 'extend', 'extendleft',
'index', 'insert', 'maxlen', 'pop', 'popleft', 'remove', 'reverse', 'rotate']

Obousměrná fronta se vytváří konstruktorem deque, přičemž již konstruktoru lze předat prvky, které se mají do fronty uložit. Tyto prvky jsou předány ve formě n-tice, seznamu či množiny.

Využití seznamu pro konstrukci fronty:

from collections import deque
 
d = deque(["foo", "bar", "baz"])
 
print(d)

Výsledek:

deque(['foo', 'bar', 'baz'])

Využití n-tice pro konstrukci fronty:

from collections import deque
 
d = deque(("foo", "bar", "baz"))
 
print(d)

Výsledek by měl být totožný s předchozím příkladem:

deque(['foo', 'bar', 'baz'])

Využití množin pro konstrukci fronty:

from collections import deque
 
d = deque({"foo", "bar", "baz"})
 
print(d)

Výsledek by opět měl být totožný s předchozími dvěma příklady:

deque(['baz', 'bar', 'foo'])

8. Přidání prvku do fronty zleva či zprava, vložení prvku do libovolného místa fronty

V souvislosti s obousměrnými frontami je nutné nějakým způsobem označit, resp. pojmenovat oba konce fronty. V některých dokumentech nebo knihovnách se setkáme s označením „first“ a „last“, popř. „head“ a „tail“, což však může být matoucí. Namísto toho se Pythonu spíše setkáme s použitím slov „left“ a „right“, tj. jeden z konců fronty je „levý“ a druhý „pravý“.

Pro přidání prvku do fronty zprava slouží metoda append (protože v jiných strukturách append přidává prvky na konec, tedy v naší kultuře doprava):

from collections import deque
 
d = deque(["a", "b", "c"])
print(d)
 
d.append("bar")
print(d)
 
d.append("baz")
print(d)

Výsledky ukazují, že se prvky přidaly do frontyzprava:

deque(['a', 'b', 'c'])
deque(['a', 'b', 'c', 'bar'])
deque(['a', 'b', 'c', 'bar', 'baz'])

Pro přidání prvku na levý konec fronty je určena metoda pojmenovaná appendleft:

from collections import deque
 
d = deque(["a", "b", "c"])
print(d)
 
d.appendleft("bar")
print(d)
 
d.appendleft("baz")
print(d)

Nyní budou prvky přidány zleva:

deque(['a', 'b', 'c'])
deque(['bar', 'a', 'b', 'c'])
deque(['baz', 'bar', 'a', 'b', 'c'])

Pro vložení prvku do libovolného místa ve frontě slouží metoda insert, které je navíc nutné předat index prvku:

from collections import deque
 
d = deque(["a", "b", "c"])
print(d)
 
d.insert(1, "bar")
print(d)
 
d.insert(3, "baz")
print(d)

V tomto případě budou prvky vloženy na druhé a čtvrté místo ve frontě (prvky se indexují od nuly):

deque(['a', 'b', 'c'])
deque(['a', 'bar', 'b', 'c'])
deque(['a', 'bar', 'b', 'baz', 'c'])

9. Odstranění nejlevějšího či nejpravějšího prvku z fronty

V předchozí kapitole jsme si ukázali, jak lze přidávat prvky do obousměrné fronty. Opakem této operace je odebrání prvku zleva nebo zprava. Tyto operace se jmenují pop a popleft. Nejprve si ukažme vliv metody pop:

from collections import deque
 
d = deque(["foo", "bar", "baz"])
print(d)
 
d.pop()
print(d)
 
d.pop()
print(d)

Fronta se postupně zmenší tak, že se odstraní prvky zprava:

deque(['foo', 'bar', 'baz'])
deque(['foo', 'bar'])
deque(['foo'])

O odstranění prvků zleva se stará metoda pojmenovaná popleft:

from collections import deque
 
d = deque(["foo", "bar", "baz"])
print(d)
 
d.popleft()
print(d)
 
d.popleft()
print(d)

Povšimněte si rozdílů ve výsledcích oproti předchozímu skriptu:

deque(['foo', 'bar', 'baz'])
deque(['bar', 'baz'])
deque(['baz'])

V tomto případě jsou tedy prvky odstraňovány zleva (od začátku fronty).

10. Metody reverse a rotate

Poslední dvě metody, s nimiž se v souvislosti se standardní obousměrnou frontou seznámíme, jsou metody pro otočení všech prvků ve frontě a taktéž pro rotaci prvků ve frontě (fronta se tedy bude chovat tak, jakoby se jednalo o kruhový buffer – ring buffer). Obě zmíněné metody modifikují původní frontu.

Otočení prvků ve frontě se provádí metodou reverse:

from collections import deque
 
d = deque(["foo", "bar", "baz"])
print(d)
 
d.reverse()
print(d)

Výsledkem bude fronta modifikovaná tak, že bude obsahovat stejné prvky, ale v opačném pořadí:

deque(['foo', 'bar', 'baz'])
deque(['baz', 'bar', 'foo'])

Rotace prvků ve frontě je zajištěna metodou nazvanou rotate:

from collections import deque
 
d = deque(["foo", "bar", "baz"])
print(d)
 
d.rotate()
print(d)

A takto vypadá výsledek:

deque(['foo', 'bar', 'baz'])
deque(['baz', 'foo', 'bar'])

11. Multimnožiny (multiset)

Jednou z nestandardních datových struktur sloužících pro uložení a uchování hodnot, je takzvaná multimnožina (multiset). Jedná se o upravenou formu klasického typu set, který byl popsán v předchozích kapitolách. Ovšem zatímco u běžné množiny je zaručeno, že každý prvek je v ní uložen maximálně jedenkrát, protože prvky musí být unikátní, u multimnožin se pamatuje nejenom to, zda nějaký prvek obsahuje či nikoli, ale navíc i počet uložených prvků se stejnou hodnotou. To je vlastnost, kterou se multimnožina přibližuje standardnímu kontejneru Counter. Jak multimnožinou, tak i právě zmíněným kontejnerem Counter se budeme podrobněji zabývat v navazujícím článku. Jedná se přitom o důležité kontejnery, protože poměrně ve velkém množství kódů můžeme vidět snahu o novou implementaci multimnožiny či čítače s využitím slovníků atd. (což je většinou zbytečné plýtvání časem).

Na závěr této kapitoly si ještě uveďme důkaz, že multimnožina je z pohledu programovacího jazyka Python skutečným kontejnerem, tj. že implementuje metodu __contains__ (a tím pádem podporuje operátor in):

>>> from multiset import Multiset
 
>>> dir(Multiset)
 
['__add__', '__and__', '__bool__', '__class__', '__contains__',
'__copy__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__getitem__', '__getstate__',
'__gt__', '__hash__', '__iand__', '__imul__', '__init__', '__init_subclass__',
'__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__',
'__module__', '__mul__', '__ne__', '__new__', '__or__', '__radd__', '__rand__',
'__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__ror__', '__rsub__',
'__rxor__', '__setattr__', '__setitem__', '__setstate__', '__sizeof__',
'__slots__', '__str__', '__sub__', '__subclasshook__', '__xor__',
'_as_mapping', '_as_multiset', '_elements', '_issubset', '_issuperset',
'_total', 'add', 'clear', 'combine', 'copy', 'difference', 'difference_update',
'discard', 'distinct_elements', 'from_elements', 'get', 'intersection',
'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'items',
'multiplicities', 'pop', 'remove', 'setdefault', 'symmetric_difference',
'symmetric_difference_update', 'times', 'times_update', 'union',
'union_update', 'update', 'values']

12. Multislovníky (multidict)

S multimnožinou do určité míry souvisí i další nestandardní typ kontejneru, který se nazývá multislovník (multidict). Tento kontejner se liší od běžných slovníků, v nichž je zaručena jednoznačnost klíčů, což znamená, že klíč lze použít ve formě selektoru jediné hodnoty (popř. samozřejmě klíč, resp. dvojice klíč+hodnota vůbec nemusí ve slovníku existovat). V multislovnících je tomu ovšem jinak, protože může existovat větší množství dvojic klíč+hodnota se stejným klíčem.

I když to tak možná nemusí na první pohled vypadat, mají multislovníky poměrně důležitou funkci, protože například mohou sloužit pro uložení HTTP hlaviček posílaných společně s dotazem (či odpovědí), popř. pro uložení parametrů dotazu, protože zde se může vyskytnout několik hodnot pod stejným jménem. I s tímto datovým typem se podrobněji seznámíme v navazujícím článku. Zde si jen pro úplnost ukažme, že i multislovník je z pohledu programovacího jazyka Python skutečným kontejnerem:

>>> from multidict import MultiDict
 
>>> dir(MultiDict)
['__class__', '__class_getitem__', '__contains__', '__delattr__',
'__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'add', 'clear',
'copy', 'extend', 'get', 'getall', 'getone', 'items', 'keys', 'pop', 'popall',
'popitem', 'popone', 'setdefault', 'update', 'values']

13. Balíček python-box zajišťující výběr hodnot s využitím klíčů nebo atributů

V závěrečné části dnešního článku si popíšeme velmi užitečný balíček, který se jmenuje python-box. Jedná se o balíček s definicí třídy Box, která do značné míry napodobuje chování standardního slovníku. Ovšem navíc umožňuje výběr hodnot ze slovníku nikoli jen s využitím klíče, ale tak, jakoby se jednalo o atribut objektu. To tedy znamená, že k hodnotě uložené v Boxu se jménem b pod klíčem „foo“ je možné přistupovat následujícími dvěma způsoby:

b["foo"]
 
b.foo

Jedná se o chování, které můžeme nalézt v programovacím jazyku Lua, v němž je možné k prvkům uloženým k tabulkách taktéž přistupovat přes klíč či přes atribut:

t = {}
 
t.foo = 1
print(t.foo)
 
t["foo"] = 2
print(t.foo)
 
t.foo = nil
print(t["foo"])
Poznámka: povšimněte si, že oba způsoby zápisu selektoru jsou v tomto případě ekvivalentní.

Na tomto místě vás asi napadlo, jak tento koncept může fungovat v případě, že klíč neodpovídá syntaxi Pythonu. Python například neumožňuje, aby atribut měl jméno „0“, „my-test“ atd. Kontejner Box dokáže tento problém řešit, i když jen do určité míry, což si ukážeme v demonstračních příkladech.

14. Instalace balíčku python-box

Vzhledem k tomu, že kontejner Box nepatří mezi kontejnery definované ve standardní knihovně programovacího jazyka Python, je nutné příslušný balíček doinstalovat. Není to nic složitého, protože nemá žádné další závislosti:

$ pip3 install python-box
 
Defaulting to user installation because normal site-packages is not writeable
Collecting python-box
  Downloading python_box-7.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.3 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.3/4.3 MB 3.4 MB/s eta 0:00:00
Installing collected packages: python-box
Successfully installed python-box-7.1.1

V interaktivní smyčce jazyka Python se přesvědčíme, že lze modul box naimportovat a zobrazit si nápovědu ke třídě Box:

Help on class Box in module box.box:
 
class Box(builtins.dict)
 |  Box(*args: 'Any', default_box: 'bool' = False, default_box_attr: 'Any' = <object object at 0x7f5254b84750>, default_box_none_transform: 'bool' = True, default_box_create_on_get: 'bool' = True, frozen_box: 'bool' = False, camel_killer_box: 'bool' = False, conversion_box: 'bool' = True, modify_tuples_box: 'bool' = False, box_safe_prefix: 'str' = 'x', box_duplicates: 'str' = 'ignore', box_intact_types: 'Union[Tuple, List]' = (), box_recast: 'Optional[Dict]' = None, box_dots: 'bool' = False, box_class: "Optional[Union[Dict, Type['Box']]]" = None, box_namespace: 'Union[Tuple[str, ...], Literal[False]]' = (), **kwargs: 'Any')
 |
 |  Improved dictionary access through dot notation with additional tools.
 |
 |  :param default_box: Similar to defaultdict, return a default value
 |  :param default_box_attr: Specify the default replacement.
 |      WARNING: If this is not the default 'Box', it will not be recursive
 |  :param default_box_none_transform: When using default_box, treat keys with none values as absent. True by default
 |  :param default_box_create_on_get: On lookup of a key that doesn't exist, create it if missing
 |  :param frozen_box: After creation, the box cannot be modified
 |  :param camel_killer_box: Convert CamelCase to snake_case
 |  :param conversion_box: Check for near matching keys as attributes
 |  :param modify_tuples_box: Recreate incoming tuples with dicts into Boxes
 |  :param box_safe_prefix: Conversion box prefix for unsafe attributes
 |  :param box_duplicates: "ignore", "error" or "warn" when duplicates exists in a conversion_box
 |  :param box_intact_types: tuple of types to ignore converting
 |  :param box_recast: cast certain keys to a specified type
 |  :param box_dots: access nested Boxes by period separated keys in string
 |  :param box_class: change what type of class sub-boxes will be created as
 |  :param box_namespace: the namespace this (possibly nested) Box lives within

15. Datová struktura Box je kontejnerem

Na tomto místě už pravděpodobně nebude větším překvapením, že Box je z pohledu programovacího jazyka Python kontejnerem:

>>> from box import Boxfrom box import Box
>>> dir(Box)
['_Box__box_config', '_Box__convert_and_store', '_Box__get_default',
'_Box__recast', '__add__', '__annotations__', '__class__', '__class_getitem__',
'__contains__', '__copy__', '__deepcopy__', '__delattr__', '__delitem__',
'__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattr__', '__getattribute__', '__getitem__', '__getstate__', '__gt__',
'__hash__', '__iadd__', '__init__', '__init_subclass__', '__ior__', '__iter__',
'__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__or__',
'__radd__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__',
'__ror__', '__setattr__', '__setitem__', '__setstate__', '__sizeof__',
'__str__', '__sub__', '__subclasshook__', '__weakref__', '_conversion_checks',
'_protected_keys', '_safe_attr', 'clear', 'copy', 'from_json', 'from_msgpack',
'from_toml', 'from_yaml', 'fromkeys', 'get', 'items', 'keys', 'merge_update',
'pop', 'popitem', 'setdefault', 'to_dict', 'to_json', 'to_msgpack', 'to_toml',
'to_yaml', 'update', 'values']

16. Příklady využití datové struktury Box

Podívejme se nyní na některé demonstrační příklady, které ukazují použití kontejneru Box v praxi. Samotná konstrukce tohoto kontejneru s předáním prvků může být realizována například tak, že se konstruktoru předá existující (již zkonstruovaný) slovník:

from box import Box
 
b = Box({"foo": 1, "bar": 2, "baz": None})
print(b)

Nyní, když již máme Box vytvořený, si můžeme otestovat, že k hodnotám lze skutečně přistupovat jak přes klíč, tak i přes atribut:

from box import Box
 
b = Box({"foo": 1, "bar": 2, "baz": None})
 
print(b["foo"])
print(b.foo)
print()
 
print(b["bar"])
print(b.bar)
print()
 
print(b["baz"])
print(b.baz)
print()

Jak se bude Box chovat v případě, že se pokusíme o přístup k neexistující hodnotě? Nejprve si vyzkoušejme čtení přes klíč:

from box import Box
 
b = Box({"foo": 1, "bar": 2, "baz": None})
 
print(b["unknown"])

To podle očekávání povede k vyhození výjimky, zde konkrétně výjimky KeyError:

KeyError: 'unknown'
 
The above exception was the direct cause of the following exception:
 
Traceback (most recent call last):
  File "/home/ptisnovs/src/most-popular-python-libs/containers/box_03.py", line 5, in <module>
    print(b["unknown"])
          ~^^^^^^^^^^^
  File "box/box.py", line 619, in box.box.Box.__getitem__
box.exceptions.BoxKeyError: "'unknown'"

Příklad nyní pozměníme tak, že budeme přistupovat k neexistujícímu prvku přes atribut:

from box import Box
 
b = Box({"foo": 1, "bar": 2, "baz": None})
 
print(b.unknown)

Opět dojde k vyhození výjimky, tentokrát ovšem typu AttributeError a nikoli KeyError:

AttributeError: 'Box' object has no attribute 'unknown'
 
The above exception was the direct cause of the following exception:
 
Traceback (most recent call last):
  File "/home/ptisnovs/src/most-popular-python-libs/containers/box_04.py", line 5, in <module>
    print(b.unknown)
          ^^^^^^^^^
  File "box/box.py", line 647, in box.box.Box.__getattr__
box.exceptions.BoxKeyError: "'Box' object has no attribute 'unknown'"

Ve chvíli, kdy jména klíčů nelze převést na korektní jméno atributu, se může zdát, že třídu Box není možné využít. Příkladem mohou být klíče uložené jako celá čísla. Taková jména atributů ovšem není možné v Pythonu použít:

from box import Box
 
b = Box({0: "foo", 1: "bar", 2: "baz"})
 
print(b[0])
print(b[1])
print(b[2])
 
print(b.0)
print(b.1)
print(b.2)

Ve skutečnosti ovšem atributy pro výběr prvků použít lze, ovšem je nutné před problematická jména zapsat prefix. Výchozím prefixem je znak „x“, takže následující demonstrační příklad je zcela korektní:

from box import Box
 
b = Box({0: "foo", 1: "bar", 2: "baz"})
 
print(b[0])
print(b[1])
print(b[2])
 
print(b.x0)
print(b.x1)
print(b.x2)

Prefix si můžete zvolit, a to při konstrukci Boxu zadáním nepovinného pojmenovaného parametru box_safe_prefix. Následující demonstrační příklad je tedy opět zcela korektní:

from box import Box
 
b = Box({0: "foo", 1: "bar", 2: "baz"}, box_safe_prefix="index_")
 
print(b[0])
print(b[1])
print(b[2])
 
print(b.index_0)
print(b.index_1)
print(b.index_2)

Ještě si ukažme průchod všemi hodnotami uloženými v boxu. Používá se zde naprosto stejný přístup, jako v případě klasického slovníku:

from box import Box
 
b = Box({"foo": 1, "bar": 2, "baz": None})
 
for key, value in b.items():
    print(key, value)

Výsledky:

foo 1
bar 2
baz None

17. Praktická ukázka: získání hodnot z několikanásobně vnořené datové struktury načtené z JSONu

Podívejme se nyní na praktickou ukázku využití kontejneru Box. Budeme potřebovat načíst následující datovou strukturu z JSONu a získat z ní například jméno použité licence, což je atribut name z podstruktury license, která je vnořená do struktury info:

{
    "openapi": "3.0.0",
    "servers": [
        {
            "url": ""
        }
    ],
    "info": {
        "description": "A very simple REST API service",
        "version": "1.0.0",
        "title": "REST API Service",
        "termsOfService": "",
        "contact": {
            "name": "Pavel Tisnovsky"
        },
        "license": {
            "name": "Apache 2.0",
            "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
        }
    },
    "tags": [],
    "paths": {
        "/": {
            "get": {
                "summary": "Returns valid HTTP 200 ok status when the service is ready",
                "description": "",
                "parameters": [],
                "operationId": "main",
                "responses": {
                    "default": {
                        "description": "Default response"
                    }
                }
            }
        },
        "/client/cluster": {
            "x-temp": {
                "summary": "Read list of all clusters from database and return it to a client",
                "description": "",
                "parameters": [],
                "operationId": "getClusters",
                "responses": {
                    "default": {
                        "description": "Default response"
                    }
                }
            },
            "get": {
                "summary": "Read list of all clusters from database and return it to a client",
                "description": "",
                "parameters": [],
                "operationId": "getClusters",
                "responses": {
                    "default": {
                        "description": "Default response"
                    }
                }
            }
        },
        "/client/cluster/{name}": {
            "get": {
                "summary": "Read cluster specified by its ID and return it to a client",
                "description": "",
                "parameters": [],
                "operationId": "getClusterById",
                "responses": {
                    "default": {
                        "description": "Default response"
                    }
                }
            },
            "post": {
                "summary": "Create a record with new cluster in a database. The updated list of all clusters is returned to client",
                "description": "",
                "parameters": [],
                "operationId": "newCluster",
                "responses": {
                    "default": {
                        "description": "Default response"
                    }
                }
            },
            "delete": {
                "summary": "Delete a cluster specified by its ID",
                "description": "",
                "parameters": [],
                "operationId": "deleteCluster",
                "responses": {
                    "default": {
                        "description": "Default response"
                    }
                }
            }
        },
        "/client/cluster/search": {
            "get": {
                "summary": "Search for a cluster specified by its ID or name",
                "description": "",
                "parameters": [
                    {
                        "name": "id",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Cluster ID",
                        "allowEmptyValue": true
                    },
                    {
                        "name": "name",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Cluster name",
                        "allowEmptyValue": true
                    }
                ],
                "operationId": "searchCluster",
                "responses": {
                    "default": {
                        "description": "Default response"
                    }
                }
            }
        }
    },
    "externalDocs": {
        "description": "Please see foo bar baz",
        "url": "https://godoc.org/..."
    },
    "security": []
}

Soubor ve formátu JSON načteme s využitím json.load a následně můžeme takto získaný slovník předat konstruktoru Box. V dalším kroku již lze přistoupit k potřebnému atributu, a to buď s využitím klíčů v roli selektorů nebo pomocí tečkové notace (a tedy atributů):

from json import load
from box import Box
 
with open("openapi.json") as fin:
    j = load(fin)
 
print(j)
print()
 
b = Box(j)
 
print(b["info"]["license"]["name"])
print(b.info.license.name)

Výsledkem by měly být následující dva řádky s totožným obsahem:

Apache 2.0
Apache 2.0

18. Klíč složený z několika selektorů

V případě, že do konstruktoru Box předáme nepovinný argument box_dots nastavený na hodnotu True, bude možné používat „složený klíč“. Takový klíč obsahuje několik selektorů (pro vnořené struktury), přičemž tyto selektory jsou v klíči odděleny tečkou. Samotný klíč je ovšem stále realizován jediným řetězcem:

Cloud 24 - tip 1

from json import load
from box import Box
 
with open("openapi.json") as fin:
    j = load(fin)
 
print(j)
print()
 
b = Box(j, box_dots=True)
 
print(b["info"]["license"]["name"])
print(b.info.license.name)
print(b["info.license.name"])

Výsledkem bude v tomto případě opět jméno licence, tentokrát získané třemi způsoby (a pochopitelně stále stejné):

Apache 2.0
Apache 2.0
Apache 2.0

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 vypsány v následující tabulce:

# Demonstrační příklad Stručný popis příkladu Cesta
1 std_tuple01.py konstrukce n-tic https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_tuple01.py
2 std_tuple02.py spojování n-tic operátorem + https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_tuple02.py
3 std_tuple03.py opakování obsahu n-tice operátorem * https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_tuple03.py
4 std_tuple04.py operátor in a n-tice https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_tuple04.py
5 std_tuple05.py generátorová notace a n-tice https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_tuple05.py
       
6 std_list01.py konstrukce seznamů https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_list01.py
7 std_list02.py spojování seznamů operátorem + https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_list02.py
8 std_list03.py opakování obsahu seznamu operátorem * https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_list03.py
9 std_list04.py řazení prvků seznamu: funkcionální a imperativní přístup https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_list04.py
10 std_list05.py otočení prvků v seznamu https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_list05.py
11 std_list06.py generátorová notace seznamů https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_list06.py
       
12 std_set01.py konstrukce množin https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_set01.py
13 std_set02.py operace nad množinami https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_set02.py
14 std_set03.py operace nad množinami https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_set03.py
15 std_set04.py rozdíl mezi operacemi discard a remove https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_set04.py
16 std_set05.py generátorová notace množin https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_set05.py
       
17 std_dict01.py konstrukce slovníků https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_dict01.py
18 std_dict02.py selektory, operace del https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_dict02.py
19 std_dict03.py chování v případě, že mazaný prvek ve slovníku neexistuje https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_dict03.py
20 std_dict04.py spojení dvou slovníků https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_dict04.py
21 std_dict05.py generátorová notace slovníků https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_dict05.py
       
22 std_deque01.py konstrukce obousměrné fronty ze seznamu https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_deque01.py
23 std_deque02.py konstrukce obousměrné fronty z n-tice https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_deque02.py
24 std_deque03.py konstrukce obousměrné fronty z množiny https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_deque03.py
25 std_deque04.py operace append https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_deque04.py
26 std_deque05.py operace appendleft https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_deque05.py
27 std_deque06.py operace insert https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_deque06.py
28 std_deque07.py operace pop https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_deque07.py
29 std_deque08.py operace popleft https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_deque08.py
30 std_deque09.py otočení prvků ve frontě https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_deque09.py
31 std_deque10.py rotace prvků ve frontě https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/std_deque10.py
       
32 box01.py konstrukce boxu https://github.com/tisnik/most-popular-python-libs/blob/master/containers/box01.py
33 box02.py přístup k prvkům boxu přes klíč i atribut https://github.com/tisnik/most-popular-python-libs/blob/master/containers/box02.py
34 box03.py chování při pokusu o přístup k neexistující hodnotě přes klíč https://github.com/tisnik/most-popular-python-libs/blob/master/containers/box03.py
35 box04.py chování při pokusu o přístup k neexistující hodnotě přes atribut https://github.com/tisnik/most-popular-python-libs/blob/master/containers/box04.py
36 box05.py chování při použití klíčů, které nejsou platnými názvy atributů v Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/containers/box05.py
37 box06.py náhrada nekorektních názvů klíčů https://github.com/tisnik/most-popular-python-libs/blob/master/containers/box06.py
38 box07.py explicitní prefix u nekorektních názvů klíčů https://github.com/tisnik/most-popular-python-libs/blob/master/containers/box07.py
39 box08.py načtení datového souboru, přístup k prvkům přes klíče i atributy https://github.com/tisnik/most-popular-python-libs/blob/master/containers/box08.py
40 box09.py využití klíče s tečkami https://github.com/tisnik/most-popular-python-libs/blob/master/containers/box09.py
41 box10.py iterace přes prvky uložené do boxu https://github.com/tisnik/most-popular-python-libs/blob/master/containers/box10.py
       
42 openapi.json datový soubor používaný v některých demonstračních příkladech https://github.com/tisnik/most-popular-python-libs/blob/master/container­s/openapi.json

20. Odkazy na Internetu

  1. collections — Container datatypes
    https://docs.python.org/3/li­brary/collections.html
  2. Balíček multidict na PyPi
    https://pypi.org/project/multidict/
  3. Balíček multiset na PyPi
    https://pypi.org/project/multiset/
  4. Repositář balíčku multidict
    https://github.com/aio-libs/multidict
  5. Repositář balíčku bidict
    https://github.com/jab/bidict
  6. Dokumentace k balíčku bidict
    https://bidict.readthedoc­s.io/en/main/
  7. Repositář balíčku DottedDict
    https://github.com/carloses­cri/DottedDict
  8. Repositář balíčku Box
    https://github.com/cdgriffith/Box
  9. Wiki (dokumentace) balíčku Box
    https://github.com/cdgrif­fith/Box/wiki
  10. Persistent data structure
    https://en.wikipedia.org/wi­ki/Persistent_data_structu­re
  11. Collections (Python)
    https://docs.python.org/3/li­brary/collections.abc.html
  12. Seriál Programovací jazyk Lua
    https://www.root.cz/seria­ly/programovaci-jazyk-lua/
  13. Operátory a asociativní pole v jazyku Lua
    https://www.root.cz/clanky/operatory-a-asociativni-pole-v-jazyku-lua/
  14. Python MultiDict Example: Map a Key to Multiple Values
    https://howtodoinjava.com/python-datatypes/python-multidict-examples/
  15. Immutable object
    https://en.wikipedia.org/wi­ki/Immutable_object
  16. pyrsistent na PyPi
    https://pypi.org/project/pyrsistent/
  17. pyrsistent na GitHubu
    https://github.com/tobgu/pyrsistent
  18. Dokumentace knihovny pyrsistent
    https://pyrsistent.readthe­docs.io/en/latest/index.html
  19. pyrthon na GitHubu
    https://github.com/tobgu/pyrthon/
  20. Mori na GitHubu
    https://github.com/swannodette/mori
  21. Mori: popis API (dokumentace)
    http://swannodette.github.io/mori/
  22. Mori: Benchmarking
    https://github.com/swanno­dette/mori/wiki/Benchmarking
  23. Functional data structures in JavaScript with Mori
    http://sitr.us/2013/11/04/functional-data-structures.html
  24. Immutable.js
    https://facebook.github.io/immutable-js/
  25. Understanding Clojure's Persistent Vectors, pt. 1
    http://hypirion.com/musin­gs/understanding-persistent-vector-pt-1
  26. Hash array mapped trie (Wikipedia)
    https://en.wikipedia.org/wi­ki/Hash_array_mapped_trie
  27. Java theory and practice: To mutate or not to mutate?
    http://www.ibm.com/develo­perworks/java/library/j-jtp02183/index.html
  28. Efficient persistent (immutable) data structures
    https://persistent.codeplex.com/
  29. Clojure (Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  30. Clojure (Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure

Byl pro vás článek přínosný?