Datové typy - 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.
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
.
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}")
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
Tuple nelze měnit:
tuple1[0] = "b" # Vyhodí výjimku
Lze ale vytvořit nový tuple z existujících
print(tuple1 + tuple2)
print(2 * tuple6)
Metody tuple:
# I tuhle krkolomnou syntaxi brzy pochopíte ;-)
", ".join(item for item in dir(tuple) if not item.startswith("_"))
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)
V tomto případě se závorky často vynechávají, takže můžeme psát
x, y, z = (1, 2, 3)
print(y)
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))
x = 3.3
# teď přiřadíme výsledek do dvou proměnných
a, b = neighbors(x)
print(f"{a} < {x} < {b}")
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)
# a nebo z textového řetězce
a, b, c = "ABC"
print(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)
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)
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)
print_all("a", "A", "Z", sep="↵\n")
Python vyhodí vyjímku pokud není počet prvků stejný
a, b, c = (1, 2, 3, 4, 5, 6)
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)
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("_"))
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
Odebrat prvek můžeme i pomocí remove
, tato metoda ale musí prvek nejprve vyhledat.
list1.remove("d") # odstranění prvku(ů)
print(list1)
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]
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)
print(a is b) # operátor is testuje identitu (objektů)
b = a.copy() # pokud chceme kopii, můžeme použít metodu copy
print(a is b)
print(a == b) # operátor == testuje hodnoty
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)
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[:])
# První dva prvky
print(l[0:2])
# Poslední tři prvky
print(l[-3:])
# Sudé prvky
print(l[::2])
# obrácené pořadí pomocí řezů
print(l[::-1])
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)
# můžeme nahradit několik prvků jinými, jejichž počet nemusí být stejný
l[1:3] = ["jedna", "dva", "tri"]
print(l)
... nebo je i mazat.
l = list(range(10))
print(l)
# vymaže prvky [0:2]
l[0:2] = []
print(l)
# nebo můžeme použít del
del l[0:2]
print(l)
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)
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)
# nyní vyzkoušíme metodu index
print(l.index("b"))
print(l.index("B"))
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)
Získávání hodnot pomocí []
print(slovnik[69]) # => 5
print(slovnik["pole_podle_skal"]) # => [1, 2, 3]
slovnik[100] # Neexistující klíč -> vyhodí výjimku
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
Metody slovníku:
", ".join(item for item in dir(dict) if not item.startswith("_"))
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
.
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í
{0}[0] # Nelze indexovat
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'}
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}")
o = {1, 1, 2, 2}
len_o = len(o)
print(f"len({o}) = {len_o}")
sum
vrací součet prvků
o = [1, 1, 2, 2]
sum_o = sum(o)
print(f"sum({o}) = {sum_o}")
o = {1, 1, 2, 2}
sum_o = sum(o)
print(f"sum({o}) = {sum_o}")
Pozor, python je silně typovaný jazyk
o = 1, 1, 2, 2, "3"
sum(o)
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)}")
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)}")
o = [True, False, True]
print(f"all({o}) = {all(o)}")
print(f"any({o}) = {any(o)}")
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))
řetězce jsou True pokud nejsou prázdné
print(bool(""))
print(bool("0"))
kontejnery tuple
, list
, dict
, set
apod. jsou True pokud nejsou prázné
print(bool([]))
print(bool(()))
print(bool({0}))
Pokud chceme otestovat jednotlivé prvky, musíme použít all
nebo any
print(f"all({{0}}) = {all({0})}")
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)}")
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)
Comments
Comments powered by Disqus