7. Kontejnery#
V této lekci se nučíte používat Pythonovské kontejnery:
Zjistíte, že:
Prvky nemusejí být stejného typu (nejsou homogenní).
Kontejnery mají v Pythonu zásadní význam a najdete je téměř všude.
Si nedokážete představit, jak může někdo programovat bez
list
,tuple
nebodict
.Pro indexování (při získávání i přiřazování prvku) se používají hranaté závorky []. Podobně jako v jazyce C se prvky indexují od 0.
7.1. Poznámka na začátek: mutable versus immutable#
Datové typy mohou být mutable (měnitelný) nebo immutable (neměnitelný). Immutable objekty (v Pythonu je v podstatě všechno objekt – mnohokrát se s tím ještě setkáme) nemoho měnit svou hodnotu. Naproti tomu mutable objekty svou hodnotu měnit mohou (aniž by ztratili svou identitu). Immutable typy jsou např čísla (int
, float
, complex
atd.), řetězce (str
a unicode
) a tuple
. Mutable typy jsou např. list
, dict
nebo set
.
7.2. Tuple#
Správně česky snad “n-tice”, nicméně často se používá prostě “tuple”.
tuple1 = (1, 'a', 5) # Základní syntax vytváření tuple (kulaté závorky)
tuple2 = 1, 'a' # Závorky nejsou povinné, ale... !
tuple3 = tuple(["a", "b"]) # Pokročilé: Vytvoření tuple z jiného kontejneru
tuple4 = tuple(range(0, 10)) # Pokročilé: Vytvoření tuple z iterátoru / generátoru
tuple5 = () # Prázdný tuple
tuple6 = ("single", ) # Tuple s jedním prvkem
tuple7 = 0, "1", (0, 1, 2) # Tuple může pochopitelně obsahovat další tuple
# A co nám vylezlo?
print(f"tuple1={tuple1}")
print(f"tuple2={tuple2}")
print(f"tuple3={tuple3}")
print(f"tuple4={tuple4}")
print(f"tuple5={tuple5}")
print(f"tuple6={tuple6}")
print(f"tuple7={tuple7}")
tuple1=(1, 'a', 5)
tuple2=(1, 'a')
tuple3=('a', 'b')
tuple4=(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
tuple5=()
tuple6=('single',)
tuple7=(0, '1', (0, 1, 2))
K získání prvku tuple použijeme hranaté závorky:
print(tuple4[0]) # První prvek
print(tuple4[-1]) # Poslední prvek
print(tuple4[-2]) # Předposlední prvek
0
9
8
Tuple nelze měnit:
tuple1[0] = "b" # Vyhodí výjimku
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[6], line 1
----> 1 tuple1[0] = "b" # Vyhodí výjimku
TypeError: 'tuple' object does not support item assignment
Lze ale vytvořit nový tuple z existujících
print(tuple1 + tuple2)
print(2 * tuple6)
(1, 'a', 5, 1, 'a')
('single', 'single')
Metody tuple:
# I tuhle krkolomnou syntaxi brzy pochopíte ;-)
", ".join(item for item in dir(tuple) if not item.startswith("_"))
'count, index'
Rozbalování (unpacking)#
Tuple lze použít pro přiřazení hodnot do více proměnných najednou. Např.
(x, y, z) = (1, 2, 3)
print(y)
2
V tomto případě se závorky často vynechávají, takže můžeme psát
x, y, z = (1, 2, 3)
print(y)
2
To je užitečné zejména pro funkce, které vracejí více hodnot.
from math import ceil, floor
def neighbors(x):
"""Vrátí celá čísla a, b menší a větší než x, tj a < x < b"""
a = int(floor(x))
b = int(ceil(x))
# pokud je x celé číslo, musíme přičíst/odečíst 1
if a == x:
a -= 1
b += 1
return a, b
# uvidíme, že funkce vrací tuple
print(neighbors(-1))
(-2, 0)
x = 3.3
# teď přiřadíme výsledek do dvou proměnných
a, b = neighbors(x)
print(f"{a} < {x} < {b}")
3 < 3.3 < 4
Na pravé straně může být jakýkoli iterabilní objekt (o iterátorech více později), např. seznam (o tom se dozvíme za chvilku) nebo string.
# vezmeme prvky ze seznamu
a, b, c = [1, 2, 3]
print(a, b, c)
1 2 3
# a nebo z textového řetězce
a, b, c = "ABC"
print(a, b, c)
A B C
Ještě více možností přináší extended unpacking a additional unpacking generalizations.
# do c se přiřadí všechny zbývající prvky v podobě seznamu
a, b, *c = (1, 2, 3, 4, 5, 6)
print(a)
print(b)
print(c)
1
2
[3, 4, 5, 6]
Důležitá je samozřejmě ona hvězdička, která může být i uprostřed.
a, *b, c = (1, 2, 3, 4, 5, 6)
print(a)
print(b)
print(c)
1
[2, 3, 4, 5]
6
Unpacking se hojně využívá i pro definice nebo volání funkcí, které mají proměnný počet argumentů. Všimněte si, že unpacking pomocí *
může použit oběma směry - jak při volání, tak při definici funkce.
def print_all(*items, sep="<->"):
print(sep.join(items))
arguments = ("a", "A", "Z")
print_all(*arguments)
a<->A<->Z
print_all("a", "A", "Z", sep="↵\n")
a↵
A↵
Z
Python vyhodí vyjímku pokud není počet prvků stejný
a, b, c = (1, 2, 3, 4, 5, 6)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[20], line 1
----> 1 a, b, c = (1, 2, 3, 4, 5, 6)
ValueError: too many values to unpack (expected 3)
7.3. List (seznam)#
List je obdobou tuple, je ovšem mutable, tj. můžeme měnit jeho prvky. Vytváří se hranatými závorkami nebo funkcí list
.
list(), [] # prázdný list
list1 = ["a", "b", "c"] # list vytvoříme pomocí [...]
list2 = [0, 0.0, "0.0"] # můžeme tam dát libovolné typy
list3 = list(tuple1) # nebo list vytvořit z tuple
print(list1)
print(list2)
print(list3)
['a', 'b', 'c']
[0, 0.0, '0.0']
[1, 'a', 5]
Metod obsahuje list více než tuple, což vyplývá z toho, že je mutable.
", ".join(item for item in dir(list) if not item.startswith("_"))
'append, clear, copy, count, extend, index, insert, pop, remove, reverse, sort'
Přirozené pro seznam je přidávání na konec pomocí append
a odebírání z konce pomocí pop
:
list1.append("d") # přidání prvku
print(list1) # list1 se změnil!
list1.sort(reverse=True)
print(list1)
print(list1.pop()) # vyjme poslední prvek
print(list1) # který je z původního listu vymazán
['a', 'b', 'c', 'd']
['d', 'c', 'b', 'a']
a
['d', 'c', 'b']
Odebrat prvek můžeme i pomocí remove
, tato metoda ale musí prvek nejprve vyhledat.
list1.remove("d") # odstranění prvku(ů)
print(list1)
['c', 'b']
Pomocí vnořených seznamů lze vytvářet “vícerozměrné” seznamy.
l = [[11, 12], [21, 22]] # "vícerozměnrý" list
print(l[0][0]) # prvek [0,0]
11
Všechny mutable typy (a tedy i list) v podstatě reference nebo, chcete-li, ukazatele (pointery). Na to musíme pamatovat, abychom nechtěně nepřepisovali obsah jiné proměnné.
a = [1, 2, 3]
b = a # b je identický list jako a (ne jeho kopie)
b.insert(0, 0) # protože list je mutable, zmení se b i a
print(a)
[0, 1, 2, 3]
print(a is b) # operátor is testuje identitu (objektů)
True
b = a.copy() # pokud chceme kopii, můžeme použít metodu copy
print(a is b)
print(a == b) # operátor == testuje hodnoty
False
True
Jakmile b
změníme, už se nezmění a
jelikož jsme vytvořili nový objekt. Už se ani nerovnají podle hodnot, tj. podle operátoru ==
.
b.append(5)
print(a)
print(b)
print(a == b)
[0, 1, 2, 3]
[0, 1, 2, 3, 5]
False
Indexování neboli řezání (slicing)#
Řezy jsou velice důležitým konceptem. Pro proměnný typu list
a tuple
lze řezy použít pro výběr prvku(ů) poměrně sofistikovaným způsobem, lze je použít i pro změnu seznamu. list
a tuple
umožňují tzv. jednoduchý řez (simple slice), detaily viz dokumentace. Rozšířené řezy (extended slicing) uvidíme později pro pole Numpy. Syntaxe jednoduchého řezu je
[[dolní_mez] : [horní_mez] [: [krok]]
Implicitní hodnota pro horní a dolní mez je None, pro krok je implicitné hodnota 1. Výsledek obsahuje prveky s indexy od dolní meze (včetně) až po prvky s indexy menšími než horní mez, případně s daným krokem. Na příkladech si ukážeme, jak to funguje.
# vytvoříme jednoduchý seznam (range v Pythonu 3 nevrací list, proto je lepší použít konverzi)
l = list(range(10))
# všechny prvky seznamu
print(l[:])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# První dva prvky
print(l[0:2])
[0, 1]
# Poslední tři prvky
print(l[-3:])
[7, 8, 9]
# Sudé prvky
print(l[::2])
[0, 2, 4, 6, 8]
# obrácené pořadí pomocí řezů
print(l[::-1])
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Pomocí řezů můžeme do seznamu prvky přidávat (pro přidávání existuje ještě metoda insert
)
l = list(range(10))
print(l)
l[:1] = ["jsem prvni"]
print(l)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
['jsem prvni', 1, 2, 3, 4, 5, 6, 7, 8, 9]
# můžeme nahradit několik prvků jinými, jejichž počet nemusí být stejný
l[1:3] = ["jedna", "dva", "tri"]
print(l)
['jsem prvni', 'jedna', 'dva', 'tri', 3, 4, 5, 6, 7, 8, 9]
… nebo je i mazat.
l = list(range(10))
print(l)
# vymaže prvky [0:2]
l[0:2] = []
print(l)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[2, 3, 4, 5, 6, 7, 8, 9]
# nebo můžeme použít del
del l[0:2]
print(l)
[4, 5, 6, 7, 8, 9]
Už víme, že seznam (list
) je mutable a že kopii můžeme vytvořit pomocí metody copy
. Můžeme použít ale i řez [:], tj.
a = ["a"]
b = a[:]
# otestujeme pomocí is, jestli jsou a, b identické obejkty
print(a is b)
False
Cvičení: Vytvořte seznam programovacích jazyků (alespoň pěti), které znáte (nezapomeňte Python :).
Pomocí metody
sort
ho setřiďte ho abecedy a vypište.Vypište první a poslední jazyk.
Na konec seznamu přidejte jazyk “Lisp”.
Vymažte ze seznamu vše kromě prvních dvou a posledních dvou jazyků.
Hledání v seznamech#
Pro testování, zda je (není) prvek v seznamu (nebo tuple) použijeme klíčové slovo in
(not in
). Dále existuje metoda index
, která vrací polohu nějakého prvku.
l = ["a", "A", "b", "ABC", 1, "2"]
# použijeme in pro test jestli list obsohuje "b" a "B"
print("b" in l)
print("B" in l)
print("B" not in l)
True
False
True
# nyní vyzkoušíme metodu index
print(l.index("b"))
2
print(l.index("B"))
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[42], line 1
----> 1 print(l.index("B"))
ValueError: 'B' is not in list
7.4. Dictionary (slovník)#
Slovník je asociativní pole, jehož klíči jsou jakékoliv hashovatelné objekty (čísla, řetězce, tuply a většina uživatelsky definovatelných tříd). Jako klíč se nedají použít např. mutable kontejnery (dict
, list
apod.), které jsou nehashovatelné.
prazdny_slovnik = dict()
prazdny_slovnik2 = {} # Ekvivalentní zápis
slovnik = {69: 5, "pole_podle_skal" : [1, 2, 3]} # Různé typy uložených hodnot
print(slovnik)
{69: 5, 'pole_podle_skal': [1, 2, 3]}
Získávání hodnot pomocí []
print(slovnik[69]) # => 5
5
print(slovnik["pole_podle_skal"]) # => [1, 2, 3]
[1, 2, 3]
slovnik[100] # Neexistující klíč -> vyhodí výjimku
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
Cell In[46], line 1
----> 1 slovnik[100] # Neexistující klíč -> vyhodí výjimku
KeyError: 100
print(slovnik.get(100)) # Varianta, která výjimku nevyhodí (a vrátí None)
print(slovnik.get(100, "vychozi_hodnota")) # Varianta, která vrátí definovanou výchozí hodnotu, když se prvek nenajde
None
vychozi_hodnota
Metody slovníku:
", ".join(item for item in dir(dict) if not item.startswith("_"))
'clear, copy, fromkeys, get, items, keys, pop, popitem, setdefault, update, values'
Pro test, zda slovník (ne)obsahuje položku s daným klíčem, slouží stejně jako pro tuple a list operátor in
, resp. not in
.
7.5. Set (množina)#
Neřazený a neindexovatelný seznam hashovatelných prvků, ve kterém každý prvek smí být pouze jednou. V zásadě se chová podobně jako slovník obsahující jenom klíče.
print({"srdce", "piky", "kule", "krize"})
print(set(("a", "a", "a", "b", "b"))) # Duplicitní prvky se odstraní
{'srdce', 'krize', 'piky', 'kule'}
{'b', 'a'}
{0}[0] # Nelze indexovat
<>:1: SyntaxWarning: 'set' object is not subscriptable; perhaps you missed a comma?
<>:1: SyntaxWarning: 'set' object is not subscriptable; perhaps you missed a comma?
/var/folders/dm/gbbql3p121z0tr22r2z98vy00000gn/T/ipykernel_74006/326244760.py:1: SyntaxWarning: 'set' object is not subscriptable; perhaps you missed a comma?
{0}[0] # Nelze indexovat
/var/folders/dm/gbbql3p121z0tr22r2z98vy00000gn/T/ipykernel_74006/326244760.py:1: SyntaxWarning: 'set' object is not subscriptable; perhaps you missed a comma?
{0}[0] # Nelze indexovat
/var/folders/dm/gbbql3p121z0tr22r2z98vy00000gn/T/ipykernel_74006/326244760.py:1: SyntaxWarning: 'set' object is not subscriptable; perhaps you missed a comma?
{0}[0] # Nelze indexovat
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[50], line 1
----> 1 {0}[0] # Nelze indexovat
TypeError: 'set' object is not subscriptable
Pro množiny jsou k dispozici operátory (případně metody)
|
(union
)&
(instersection
)-
(difference
)^
(symmetric_difference
)<
,<=
(issubset
)>
,>=
(issuperset
)
Cvičení::
Vytvořte slovník ve kterém přiřadíte programovacím jazykům body podle oblíbenosti (1 - 10).
Zjistěte pomocí množinových operací, které z vašich programovacích jazyků jsou a které nejsou společné s množinou
COMMON_LANGUAGES
definovanou níže.
COMMON_LANGUAGES = {'Python', 'C', 'Java'}
7.6. Vestavěné funkce pro práci s kontejnery#
Python má několik důležitých funkcí, které se hodí pro práci s kontejnery.
len
vrací počet prvků
o = [1, 1, 2, 2]
len_o = len(o)
print(f"len({o}) = {len_o}")
len([1, 1, 2, 2]) = 4
o = {1, 1, 2, 2}
len_o = len(o)
print(f"len({o}) = {len_o}")
len({1, 2}) = 2
sum
vrací součet prvků
o = [1, 1, 2, 2]
sum_o = sum(o)
print(f"sum({o}) = {sum_o}")
sum([1, 1, 2, 2]) = 6
o = {1, 1, 2, 2}
sum_o = sum(o)
print(f"sum({o}) = {sum_o}")
sum({1, 2}) = 3
Pozor, python je silně typovaný jazyk
o = 1, 1, 2, 2, "3"
sum(o)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[56], line 2
1 o = 1, 1, 2, 2, "3"
----> 2 sum(o)
TypeError: unsupported operand type(s) for +: 'int' and 'str'
min
a max
vrací nejmenší, resp. největší prvky.
o = [1, 2, -1, -10, 0]
print(f"min({o}) = {min(o)}")
print(f"max({o}) = {max(o)}")
min([1, 2, -1, -10, 0]) = -10
max([1, 2, -1, -10, 0]) = 2
sorted
vrací setříděné prvky pole.
reversed
vrací prvky pozpátku (pomocí iterátoru).
all
, any
a meší odbočka k bool
#
all
a any
vrátí logické and
, resp. or
aplikované mezi všemi prvky.
o = [True, True, True]
print(f"all({o}) = {all(o)}")
print(f"any({o}) = {any(o)}")
all([True, True, True]) = True
any([True, True, True]) = True
o = [True, False, True]
print(f"all({o}) = {all(o)}")
print(f"any({o}) = {any(o)}")
all([True, False, True]) = False
any([True, False, True]) = True
Při této příležitosti ještě odbočíme a ukážeme si, jak Python převádí cokoli (tj. objekt jakéhokoli typy) na bool
, tj. na True
nebo False
. Tento převod můžeme udělat explicitně pomocí samotné funkce bool
, děje se ale také implicitně v blocích typu if
nebo while
a také při použití all
a any
.
všechna čísla kromě 0 jsou True
print(bool(0))
print(bool(0.0))
print(bool(0.0 + 0j))
print(bool(-1))
False
False
False
True
řetězce jsou True pokud nejsou prázdné
print(bool(""))
print(bool("0"))
False
True
kontejnery tuple
, list
, dict
, set
apod. jsou True pokud nejsou prázné
print(bool([]))
print(bool(()))
print(bool({0}))
False
False
True
Pokud chceme otestovat jednotlivé prvky, musíme použít all
nebo any
print(f"all({{0}}) = {all({0})}")
all({0}) = False
Pozor na all a any logiku prázdných kontejnerů, nemusí být zcela intuitivní.
o = []
print(f"all({o}) = {all(o)}")
print(f"any({o}) = {any(o)}")
all([]) = True
any([]) = False
Cvičení#
Rozšiřte hru oko bere z předchozí kapitoly o zaznamenávání a výpis tažených karet. T.j. uživatel se na konci hry dozví, které karty byly taženy. Použijte k tomu seznam.
Pokročilejší verze: Místo pouhých čísel s hodnotou karet pracujte s názvy karet. Uložte si hodnoty jednotlivých karet do slovníku.
Hodnoty karet:
karty 7 – 10 si nechávají hodnotu ( sedma za sedm, osma, za osm, … )
spodek a svršek za 1 bod
král za 2 body
Eso vždy za 11 (ale dvě esa mají součet 21)