Přitom – jak je v Python'u zvykem – jak hodnoty, tak klíče mohou být najednou nejrůznějších typů. Přesněji hodnotou může být cokoliv, klíč však musí být neměnný datový typ (immutable). Navíc pochopitelně klíč musí být v rámci slovníku jedinečný!
Typicky se indexuje řetězcem nebo číslem, ale stejně tak dobře můžete použít například právě n-tici, pokud se sama skládá z neměnných typů:
>>> ds = { 'a': 'ahoj', 123: 'svět', (12, 'fokus'): 666, }
>>> ds
{'a': 'ahoj', (12, 'fokus'): 666, 123: 'svět'}
>>> ds['a']
'ahoj'
>>> ds[123]
'svět'
>>> ds[ (12, 'fokus') ]
666
Slovníky jsou v podstatě hash-tabulky optimalizované na vyhledávání hodnot podle klíčů.
Nejsnadněji (bez využití jiné datové struktury a speciálních metod) slovník vytvoříme přímou notací:
>>> ds = { 'a': 1, 'b': [1,2,3], 'c': "ahoj", }
>>> type(ds)
<class 'dict'>
>>> ds
{'a': 1, 'c': 'ahoj', 'b': [1, 2, 3]}
Už vidíme, proč (mimo jiné) slovníky nepatří mezi sekvence – není u nich zaručeno pořadí prvků.
PS: Přesněji řečeno nebylo do Python'u 3.7.
Druhou možností je za pomoci funkce dict() převést na slovník vhodnou vstupní datovou strukturu:
Někdy se nám může hodit zavést slovník, který má již určitý počet prvků k dispozici a každý z nich má stejnou výchozí hodnotu. K tomu slouží metoda fromkeys(seq[, value]), která zavede slovník s klíči ze sekvence seq a případnou výchozí hodnotou value (jinak None):
>>> ds = {}.fromkeys( range(4) )
>>> ds
{0: None, 1: None, 2: None, 3: None}
>>> ds = {}.fromkeys( range(4), '' )
>>> ds
{0: '', 1: '', 2: '', 3: ''}
Dejte si ale pozor, pokud value bude proměnná datová struktura – v tu chvíli narazíte na nečekané historické chování, protože výchozí hodnota je opravdu sdílená všemi klíči!
Ačkoli slovníky nepatří mezi sekvence, některé z vlastností mají – díky své struktuře „balíků na hodnoty“ – společné:
>>> ds = { 'a': 1, 'b': [1,2,3], 'c': "ahoj", }
# dotaz na počet dvojic klíč-hodnota
>>> len(ds)
3
# dotaz na výskyt klíče
>>> 'a' in ds
True
>>> 123 in ds
False
# dotaz na hodnotu odpovídající klíči 'b'
>>> ds['b']
[1, 2, 3]
Všimněte si obzvláště, že slovník je struktura zařízená na přístup podle klíčů – základní „přirozené“ operace se slovníkem se odehrávají na úrovni klíčů, nikoli hodnot.
Podoba se vztahuje dokonce i na procházení prvků slovníku smyčkou for-in. Je však zřejmé, že když slovníky obsahují dvojce závislých hodnot (klíč—hodnota), musí si smyčka „vybrat“, jakým způsobem je zobrazí. Nepřekvapivě základní volbou je zobrazení přes klíče:
# přirozený cyklus přes klíče slovníku
>>> for key in ds:
... print(key)
...
a
c
b
Varianta s enumerate() funguje také, ale jelikož pořadí výpisu prvků je v podstatě „náhodné“, tak má poněkud omezenější použití, než u sekvenčních typů.
Chcete-li zároveň s klíči vypsat i odpovídající hodnoty, řešení je jednoduché:
>>> for key in ds:
... print(key, ds[key])
...
a 1
c ahoj
b [1, 2, 3]
Existují dva základní způsoby jak přistupovat k prvkům slovníku:
známe-li klíč, můžeme pomocí něj získat odpovídající hodnotu (viz slovník[klíč] z předchozího slajdu)
pomocí smyčky přes vybraný „pohled“ na slovník probrat prvky slovníku jeden po druhém (ovšem s víceméně náhodným pořadím)
Co se „pohledů“ (views) na slovník (označme ho ds) týká, jsou tři různé:
ds.keys() – pohled přes klíče
ds.values() – pohled přes hodnoty
ds.items() – pohled přes n-tice (klíč, hodnota)
...a sdílejí stejné vlastnosti:
každého pohledu se můžeme zeptat na počet jeho členů pomocí funkce len()
přes každý pohled můžeme iterovat (např. pomocí smyčky for in)
každý pohled můžeme testovat na přítomnost prvků pomocí operátoru in
Pořadí uložení (a následně vypisování) prvků ve slovníku záleží na konkrétní implementaci Python'u a historii vkládání a odstraňování prvků slovníku. Pokud ale během přístupu neprovedeme ve slovníku žádné změny, budou všechny tři pohledy vracet prvky ve stejném pořadí.
PS: Od verze Python'u 3.7 je po implementacích požadováno, aby si pamatovaly pořadí klíčů v pořadí jejich zanesení do slovníku.
Pohled přes klíče je pohledem „základním“ a vlastně jsme ho už viděli v akci:
>>> ds = { 'a': 1, 'b': [1,2,3], 'c': "ahoj", }
>>> ds.keys()
dict_keys(['a', 'c', 'b'])
>>> len( ds.keys() )
3
>>> 'a' in ds.keys()
True
>>> for key in ds.keys():
... print(key)
...
a
c
b
Čili pro většinu praktických aplikací část .keys() vůbec nemusíme uvádět, je brána jako implicitní.
Jelikož přístup k hodnotám slovníku je realizován přes klíče, máme při tomto základním způsobu získávání informací o slovníku přístup jak ke klíčům, tak k jim odpovídajícím hodnotám.
Pohled přes hodnoty je doplňkovým k pohledu přes klíče:
>>> ds = { 'a': 1, 'b': [1,2,3], 'c': "ahoj", }
>>> ds.values()
dict_values([1, 'ahoj', [1, 2, 3]])
>>> len( ds.values() )
3
>>> 'ahoj' in ds.values()
True
>>> for val in ds.values():
... print(val)
...
1
ahoj
[1, 2, 3]
Oba pohledy prochází prvky slovníku ve stejném pořadí (i když až do Python'u 3.7 víceméně „náhodném“).
Narozdíl od pohledu přes klíče se při pohledu přes hodnoty na odpovídající prvek v páru nedostaneme – heš-tabulka je jednosměrná.
Posledním – a nejúplnějším – pohledem na slovník je pohled přes páry klíč-hodnota:
>>> ds = { 'a': 1, 'b': [1,2,3], 'c': "ahoj", }
>>> ds.items()
dict_items([('a', 1), ('c', 'ahoj'), ('b', [1, 2, 3])])
>>> len( ds.items() )
3
>>> ('a', 1) in ds.items()
True
>>> for (key, val) in ds.items():
... print(key, val)
...
a 1
c ahoj
b [1, 2, 3]
Vidíme, že prvky slovníku jsou opět procházeny ve stejném pořadí. Tentokrát jsou ale získávány ve formě n-tice tvaru (klíč, hodnota), resp. (klíč, ds[klíč]).
Základní způsob pro přístup k prvkům jsme již několikrát viděli:
..měli bychom před každým dotazem kontrolovat, zda příslušný klíč ve slovníku vůbec je (nebo odchytávat výjimku a zachovat se podle toho). S pomocí metody get(KLÍČ[, výchozí_hodnota]) se to však dá zjednodušit:
>>> x = ds.get('c')
>>> x
'ahoj'
>>> x = ds.get('d')
>>> print(x)
None
>>> x = ds.get('d', 0)
>>> x
0
Jinými slovy: Pro existující klíč se get() chová jako standardní získání hodnoty příslušející danému klíči, pro neexistující prvek automaticky odchytí výjimku a vrátí buď None (výchozí hodnota) nebo to, co si sami určíme.
Trochu podobná a poněkud kryptická metoda setdefault(key[, value]) pro existující klíč vrací jeho hodnotu, pro neexistující ho ve slovníku navíc ještě i zavede (s hodnotou value nebo výchozím None):
>>> x = ds.setdefault('c')
>>> x
'ahoj'
>>> x = ds.setdefault(123, 777)
>>> x
777
>>> ds
{'a': 1, 123: 777, 'c': 'ahoj', 'b': [1, 2, 3], 666: 'lokus'}
K čemu je vlastně dobrá, je asi líp vidět u srovnání s defaultdict.
Novou dvojici klíč-hodnota přidáme do slovníku velmi podobným způsobem, jako se ptáme na existující:
Do slovníku můžeme přidat více dvojic klíč-hodnota najednou pomocí metody update(SLOVNÍK):
>>> ds = {}
>>> ds
{}
>>> ds.update( {'a': 1, 'b': [1, 2, 3]} )
>>> ds
{'a': 1, 'b': [1, 2, 3]}
Podobně jako u tvorby slovníku však parametrem může být i jiná vhodná datová struktura:
>>> ds.update( [('c', 'ahoj'), (666, 'lokus')] )
>>> ds
{'a': 1, 'c': 'ahoj', 'b': [1, 2, 3], 666: 'lokus'}
Pokud některé z přidávaných klíčů ve slovníků již existují, dojde (nepřekvapivě) k nahrazení jejich hodnot novými.
Již nepotřebné dvojce klíč-hodnota se zbavíme snadno pomocí univerzálního del:
Slovník můžeme také prvek po prvku „zničit“ metodou popitem(), která vrací dvojici (klíč, hodnota) (a to samozřejmě v „náhodném“ pořadí):
>>> ds = { 'a': 'ahoj', 123: 'svět', (12, 'fokus'): 666, }
>>> ds
{'a': 'ahoj', (12, 'fokus'): 666, 123: 'svět'}
>>> ds.popitem()
('a', 'ahoj')
Pokus o zavolání popitem() na prázdném slovníku vrátí KeyError.
Od verze Python'u 3.7 se prvky vrazí v pořadí LIFO („poslední dovnitř, první ven“).
V podobném duchu operuje metoda pop(key[, default]), která odstraní prvek s klíčem key a vrátí jeho hodnotu (nebo default):
>>> ds = { 'a': 'ahoj', 123: 'svět', (12, 'fokus'): 666, }
>>> ds
{'a': 'ahoj', (12, 'fokus'): 666, 123: 'svět'}
>>> ds.pop('a')
'ahoj'
>>> ds
{(12, 'fokus'): 666, 123: 'svět'}
Pokud default neuvedete a key není platným klíčem, dočkáte se výjimky KeyError.
Proč nemají metody pop() a popitem() prohozené názvy, aby se svým chováním víc podobaly metodě pop() na seznamu, je mi záhadou.
Celý slovník pak můžeme smazat pomocí jeho metody clear():
>>> ds.clear()
>>> ds
{}
Jako jedna z mála datových struktur nabízí slovník i vlastní metodu pro tvorbu kopie – copy(). Jde ovšem o kopii mělkou, tzn. že proměnné (mutable) datové typy jsou pouze odkazovány, tudíž jakákoliv změna v nich se projeví i v kopii:
# pomocný seznam, tedy proměnná datová struktura
>>> xs = ['a', 'b', 'c']
# a) Zaveďme slovník, který obsahuje podseznam..
>>> zs = { 1: xs }
# ..a mělce jej zkopírujme:
>>> zs2 = zs.copy()
# Výsledek je zatím nepřekvapivý:
>>> zs
{1: ['a', 'b', 'c']}
>>> zs2
{1: ['a', 'b', 'c']}
# b) Odstraňme nyní z podseznamu poslední prvek:
>>> xs.pop()
'c'
>>> xs
['a', 'b']
# Nyní už se děje něco, co ne každý asi očekával:
>>> zs
{1: ['a', 'b']}
>>> zs2
{1: ['a', 'b']}
Pro zájemce: Chcete-li zkopírovat všechny odkazované objekty nikoli odkazem, ale hodnotou, podívejte se po knihovním modulu copy a jeho metodě copy.deepcopy().
Ve výchozím stavu prochází cyklus prvky slovníku v „náhodném“* pořadí:
>>> ds = { 'a': 1, 'b': [1,2,3], 'c': "ahoj", }
>>> for key in ds:
... print(key)
...
a
c
b
* Od Python'u verze 3.7 je pořadí podle pořadí zaznamenání do slovníku.
Pomocí zabudované funkce sorted() si ale můžeme aktuální průchod pro navzájem porovnatelné klíče vynutit seřazený:
>>> ds = { 'a': 1, 'b': [1,2,3], 'c': "ahoj", }
>>> for key in sorted(ds):
... print(key)
...
a
b
c
Python 3 rozšířil generátorovou notaci – dostupnou dříve pouze u seznamů – na další dva typy. A jedním z nich je právě slovník. Obecný konstruktor vypadá velmi podobně jako u seznamu:
{ KLÍČ:HODNOTA for I in SEKVENCE [if PODMÍNKA] }
Jako u seznamu vnitřní závorky zde označují nepovinnou část výrazu. Na první části je důležité, že má strukturu KLÍČ: HODNOTA, ne už tak, že oba prvky mohou být funkcí prvků procházené sekvence.
Lepší radši rovnou příklad – jak dosáhnout stejného výsledku jako metoda fromkeys:
>>> { i:'' for i in range(4) }
{0: '', 1: '', 2: '', 3: ''}
A něco složitějšího:
>>> xs = "Ahoj, světe!"
>>> { i:x for (i, x) in enumerate(xs) }
{0: 'A', 1: 'h', 2: 'o', 3: 'j', 4: ',', 5: ' ', 6: 's', 7: 'v', 8: 'ě', 9: 't',
10: 'e', 11: '!'}
Aneb jak velmi komplikovaně přiřadit k pozici v řetězci odpovídající písmeno, když totéž zvládá přímo xs[i] ^_^
>>> xs = "Abcd abcd"
>>> { x:ord(x) for x in xs }
{'A': 65, ' ': 32, 'c': 99, 'b': 98, 'd': 100, 'a': 97}
Aneb jak pro každé písmeno v řetězci získat jeho unicodový code-point. (Jelikož klíč slovníku je jedinečný, proiterujeme sice všechna písmena řetězce, ale ve slovníku skončí každé pouze jednou.)