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.
def neighbors(x):
"""Vrátí celá čísla a, b menší a větší než x, tj a < x < b"""
from math import ceil, floor
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 = "123"
print(a, b, c)
Python 3 přidává velice užitečnou funkcionalitu v podobě extended unpacking, 3.5 pak ještě 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ů.
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ů)
from copy import copy
b = copy(a) # pokud chcepe kopii, potřebujeme modul copy
print(a is b)
print(a == b) # operátor == testuje hodnoty
b.append(5)
print(a)
print(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í modulu 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 :). Setřiďte ho podle abecedy a vypište.
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
.
Další varianty slovníku¶
collections.OrderedDict¶
V běžném slovníku jsou data neřazená. Pokud tedy procházíte slovníkem prvek po prvku, klíče budou dosti přeházené (podle toho, jak je zpracuje interní hashovací funkce). Alternativou je OrderedDict (seřazený slovník), který prvky za sebou řadí. Ovšem pozor: Řadí je podle toho, v jakém pořadí byly do slovníku vloženy, nikoliv podle abecedy nebo jiného kritéria.
import collections
pairs = (("a" , 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)) # tuple tuplů
normal = dict(pairs)
ordered = collections.OrderedDict(pairs)
print("Keys (normal dictionary): " + ", ".join(key for key in normal))
print("Keys (ordered dictionary): " + ", ".join(key for key in ordered))
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í::
- Pomocí vhodného kontejneru přiřaďte programovacím jazykům body podle oblíbenosti (1 - 10).
- Zjistěte, které z těchto jazyků jsou / nejsou společné s množinou
COMMON_LANGUAGES
definovanou níže. a. Přímo pomocí množinové operace, t.j. pomocí set onjektů. b. Pomocí for cyklu.
Můžete doplnit implementaci níže nebo vymyslet svou vlastní hezčí.
COMMON_LANGUAGES = {'Python', 'C', 'Java'}
Pole v Numpy¶
Budeme trochu předbíhat, ale univerzalita základních kontejnerů může být v mnoha případech (při vědeckých výpočtech) nevýhodou - co se týče rychlosti, ale i vzhledem k tomu, že nemáme zaručený typ prvků ani rozměry pole. Proto knihovna numpy zavádí typ vícerozměrného pole ("ndarray"), který je extrémně rychlý (vektorové operace s ním jsou implementovány v C) a velice šikovný (umožňuje snadno pracovat s mnohorozměrnými poli i jejich komplikovanými indexy). Seznámíme se s ním v některé z dalších hodin.
Na jednoduché ukázce si ukážeme, jak se liší rychlost sčítání vektorů bez a s numpy.
array1 = list(range(0, 1000000))
array2 = list(range(1000000, 2000000))
print("Bez numpy")
%timeit array3 = [array1[i] + array2[i] for i in range(0, len(array1))]
import numpy as np
array1 = np.arange(0, 1000000)
array2 = np.arange(1000000, 2000000)
print("S numpy")
%timeit array3 = array1 + array2
Jak vidíte, stejná operace (sečtení pole prvek po prvku) s využitím numpy proběhla asi 60x rychleji.
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]
print("len(%s) = %i" % (o, len(o)))
o = 1, 1, 2, 2
print("len(%s) = %i" % (o, len(o)))
o = {1, 1, 2, 2}
print("len(%s) = %i" % (o, len(o)))
sum
vrací součet prvků
o = [1, 1, 2, 2]
print("sum(%s) = %i" % (o, sum(o)))
o = 1, 1, 2, 2
print("sum(%s) = %i" % (o, sum(o)))
o = {1, 1, 2, 2}
print("sum(%s) = %i" % (o, sum(o)))
Pozor, python je silně typovaný jazyk
o = 1, 1, 2, 2, "3"
print("sum(%s) = %i" % (o, sum(o)))
min
a max
vrací nejmenší, resp. největší prvky.
o = [1, 2, -1, -10, 0]
print("min(%s) = %i" % (o, min(o)))
print("max(%s) = %i" % (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("all(%s) = %i" % (o, all(o)))
print("any(%s) = %i" % (o, any(o)))
o = [True, False, True]
print("all(%s) = %i" % (o, all(o)))
print("any(%s) = %i" % (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(all({0}))
toto už možná není zcela intuitivní
o = []
print("all(%s) = %s" % (o, all(o)))
print("any(%s) = %s" % (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. Šlo by nějak napravit bug, který jsme možná odhalili?
Comments
Comments powered by Disqus