Lekce 7 - Komprehence, lambda výrazy a funkce v Pythonu
V předešlém cvičení, Řešené úlohy k 4.-6. lekci kolekcí v Pythonu, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
V tomto tutoriálu kolekcí v Pythonu si představíme
některé pokročilejší techniky funkcionálního programování v Pythonu.
Podíváme se na seznamové, množinové a slovníkové
komprehence, lambda výrazy a na vestavěné
funkce zip()
, map()
a filter()
.
Seznamové komprehence
Seznamová komprehence umožňuje stručný zápis tvorby
seznamu z iterovatelného objektu za pomoci transformace, iterace a
případně filtru. Příklad syntaxe je následující:
novy_seznam = [prvek * 2 for prvek in jiny_seznam if prvek < 5]
.
Jednotlivé fáze tedy jsou:
- transformace - výraz, který definuje změnu původního
prvku (zde
prvek * 2
), - iterace - transformace se postupně provádí na
jednotlivé prvky iterovatelného objektu (zde
for prvek in jiny_seznam
), - filtr (nepovinný) - pokud prvek z původního
iterovatelného objektu splňuje danou podmínku, přidá se do nového seznamu,
v opačném případě se ignoruje (zde
if prvek < 5
).
Pojďme si vše ukázat v praxi. Pro porovnání použijeme i klasický
cyklus for
, pomocí kterého bychom docílili stejného
výsledku:
zdrojovy_seznam = [1, 2, 3, 4, 5] # for cyklus: novy_seznam_1 = [] for prvek in zdrojovy_seznam: if prvek < 5: novy_seznam_1.append(prvek * 2) # seznamová komprehence: novy_seznam_2 = [prvek * 2 for prvek in zdrojovy_seznam if prvek < 5] print(novy_seznam_1) print(novy_seznam_2)
Ve výstupu vidíme, že oba přístupy dávají stejný výsledek:
Konzolová aplikace
[2, 4, 6, 8]
[2, 4, 6, 8]
Vytvořit seznam tímto způsobem se doporučuje pouze v jednoduchých případech. Čím složitější seznamovou komprehenci vytvoříme, tím méně čitelná bude a může to být kontraproduktivní:
matice = [ [0, 1, 0], [1, 2, 1], [2, 0, 1], ] # seznamová komprehence: jednicky = [cislo for radek in matice for cislo in radek if cislo > 0 and cislo < 2] # nepřehledné print(jednicky) # for cyklus: jednicky = [] for radek in matice: for cislo in radek: if cislo > 0 and cislo < 2: jednicky.append(cislo) print(jednicky)
Ve výstupu opět vidíme, že oba přístupy dávají stejný výsledek:
Konzolová aplikace
[1, 1, 1, 1]
[1, 1, 1, 1]
Množinové komprehence
Výše uvedený způsob lze aplikovat i na množiny. Postup je téměř identický, jediný rozdíl je v použitých závorkách. U množin se místo hranatých používají složené:
zdrojova_sekvence = (1, 2, 3, 4, 5)
generator_dvojnasobku = (prvek * 2 for prvek in zdrojova_sekvence if prvek < 5)
nova_mnozina = set(generator_dvojnasobku)
print(nova_mnozina)
Ve výstupu vidíme vygenerovanou množinu:
Konzolová aplikace
{2, 4, 6, 8}
Množina v Pythonu nezachovává pořadí prvků, takže i když jsme vytvořili množinu s hodnotami v určitém pořadí, Python je může vrátit v libovolném pořadí. Důležité je, že všechny správné hodnoty jsou zahrnuty v množině.
Slovníkové komprehence
Komprehence lze použít i na tvorbu slovníků. Závorky se používají stejné jako u množin. Rozdíl je ten, že pro každý prvek se místo jednoho výrazu zapisují dva výrazy oddělené dvojtečkou. První představuje klíč, druhý hodnotu. Následující kód ukazuje elegantní způsob, jak vytvořit kopii slovníku, kde dojde k prohození klíčů a hodnot. Nutno dodat, že hodnoty zdrojového slovníku musí být neměnného typu:
slovnik = {"Homer": "Simpson", "Barney": "Gumble"} inverzni_slovnik = {prijmeni: jmeno for jmeno, prijmeni in slovnik.items()} print(inverzni_slovnik)
Ve výstupu pak vidíme:
Konzolová aplikace
{'Simpson': 'Homer', 'Gumble': 'Barney'}
Při hlubším zamyšlení je zřejmé, že zde chybí možnost n-ticových
komprehencí. Pro ně by se nabízel zápis pomocí kulatých závorek.
N-ticové komprehence v Pythonu neexistují. Toto omezení se
však dá obejít vytvořením seznamové komprehence a použitím vestavěné
funkce tuple()
. Kód pro n-tici sudých čísel do 20 pak vypadá
takto:
ntice = tuple([x for x in range(2, 21, 2)]) print(ntice)
Ve výstupu pak vidíme:
Konzolová aplikace
(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
Lambda výrazy
Lambda výraz vrací funkční objekt (je to tedy jiný
způsob tvorby funkce). Vytvoříme ho klíčovým slovem lambda
.
Následují nepovinné parametry oddělené čárkou a poté dvojtečka, za
kterou se píše příslušný výraz. Ten můžeme přirovnat k tělu klasické
funkce.
Oproti klasickým funkcím se liší následovně:
- Výsledná funkce nemá jméno (proto se někdy nazývá anonymní).
- K definování takovéto anonymní funkce nám musí stačit jeden řádek (přesněji jeden výraz).
Druhého bodu se dá využít např. u funkcí vyššího řádu, které přijímají jako argument jinou funkci. My se na dvě z nich podíváme na konci lekce.
Podívejme se na příklad:
def normal_fce(x, y): return x + y lambda_fce = lambda x, y: x + y f1 = normal_fce(2, 5) f2 = lambda_fce(2, 5) print(f1) print(f2)
Ve výstupu vidíme, že výsledek je v obou případech stejný:
Konzolová aplikace
7
7
Funkce
Nyní si představíme několik vestavěných funkcí pro práci s iterovatelnými objekty, které v případě vhodného použití dokáží znatelně ulehčit práci.
enumerate()
Funkce vytvoří dvojice, kde první položkou je index a druhou položkou je příslušný prvek zadaného iterovatelného objektu:
rocni_obdobi = ["jaro", "leto", "podzim", "zima"] enum_rocni_obdobi = enumerate(rocni_obdobi, start=1) for index, obdobi in enum_rocni_obdobi: print(index, obdobi)
Ve výstupu vidíme:
Konzolová aplikace
1 jaro
2 leto
3 podzim
4 zima
zip()
Funkce vytvoří n-tice ze zadaných iterovatelných objektů tak, že každá n-tice obsahuje po jednom prvku z každého iterovatelného objektu:
filmy = ['Terminator', 'Rambo', 'Poslední skaut', 'Titanic'] herci = ['Schwarzenegger', 'Stallone', 'Willis', 'Di Caprio'] roky = [1984, 1982, 1991] fhr = zip(filmy, herci, roky) for i in fhr: print(i)
Film Titanic
a herec Di Caprio
ve výstupu nejdou,
protože jim chybí odpovídající prvek z objektu roky
:
Konzolová aplikace
('Terminator', 'Schwarzenegger', 1984)
('Rambo', 'Stallone', 1982)
('Poslední skaut', 'Willis', 1991)
map()
Funkce příjímá jako svůj argument jinou funkci, kterou aplikuje na iterovatelné objekty tak, že za parametry této funkce dosadí paralelně jednotlivé prvky příslušných iterovatelných objektů. To znamená, že počet parametrů funkce se musí rovnat počtu iterovatelných objektů.
Příklad jednoparametrické funkce se seznamem:
ceny_v_kc = [25, 1500, 10_000, 500_000] def format_cen(cena): return str(cena) + ' Kc' ceny = map(format_cen, ceny_v_kc) for c in ceny: print(c)
Výstupem je:
Konzolová aplikace
25 Kc
1500 Kc
10000 Kc
500000 Kc
Příklad dvouparametrické anonymní funkce se dvěma n-ticemi různé délky:
for soucet in map(lambda x, y: x + y, (1, 2, 3, 4), (100, 200, 300)): print(soucet)
Ve výstupu vidíme:
Konzolová aplikace
101
202
303
filter()
Funkce příjímá jako svůj argument jinou funkci, kterou aplikuje na
iterovatelný objekt tak, že za parametr funkce postupně dosazuje jednotlivé
prvky tohoto objektu. Pokud funkce s daným argumentem vrátí
False
, prvek je zahozen:
cisla = [2, 5, 13, 16, 50, 55] licha_cisla = filter(lambda x: x % 2, cisla) print(list(licha_cisla))
Konzolová aplikace
[5, 13, 55]
Kombinace funkcí map()
a
filter()
V praxi se často setkáme s obraty, kde se kombinují výhody obou zmíněných funkcí:
ceny_v_kc = [25, 1500, 10_000, 500_000] ceny_do_10_000 = map(lambda cena: str(cena) + "Kc", filter(lambda cena: cena < 10_000, ceny_v_kc)) print(list(ceny_do_10_000))
Ve výstupu vidíme:
Konzolová aplikace
['25Kc', '1500Kc']
Stejného účinku nicméně docílíme pomocí seznamové komprehence následovně:
print([str(cena) + "Kc" for cena in ceny_v_kc if cena < 10_000])
V případě funkce map()
, která pracuje s více
iterovatelnými objekty, si musíme ještě vypomoci funkcí
zip()
:
print(list(map(lambda x, y: x + y, (1, 2, 3, 4), (100, 200, 300)))) print([x + y for x, y in zip((1, 2, 3, 4), (100, 200, 300))])
Vidíme, že výstup je totožný:
Konzolová aplikace
[101, 202, 303]
[101, 202, 303]
Použití seznamové komprehence místo kombinování funkcí se obecně považuje za čistější řešení. Kód je přímočařejší a lépe čitelný.
V příští lekci, ChainMap, NamedTuple a DeQue v Pythonu, si vysvětlíme, k čemu a jak se v Pythonu používají kolekce ChainMap, NamedTuple a DeQue.