7. diel - Komprehencie, lambda výrazy a funkcie v Pythone
V predchádzajúcom cvičení, Riešené úlohy k 4.-6. lekciu kolekcií v Pythone, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V tomto tutoriále kolekcií v Pythone si predstavíme
niektoré pokročilejšie techniky funkcionálneho programovania v Pythone.
Pozrieme sa na zoznamové, množinové a slovníkové
komprehencie, lambda výrazy a na vstavané
funkcie zip()
, map()
a filter()
.
Zoznamové komprehencie
Zoznamová komprehencie umožňuje stručný zápis tvorby
zoznamu z iterovateľného objektu pomocou transformácie, iterácie a
prípadne filtra. Príklad syntaxe je nasledujúci:
novy_seznam = [prvek * 2 for prvek in jiny_seznam if prvek < 5]
.
Jednotlivé fázy teda sú:
- transformácia - výraz, ktorý definuje zmenu pôvodného
prvku (tu
prvek * 2
), - iterácia - transformácia sa postupne vykonáva na
jednotlivé prvky iterovateľného objektu (tu
for prvek in jiny_seznam
), - filter (nepovinný) - ak prvok z pôvodného
iterovateľného objektu spĺňa danú podmienku, pridá sa do nového zoznamu,
v opačnom prípade sa ignoruje (tu
if prvek < 5
).
for
, pomocou ktorého by sme docielili rovnaký výsledok:
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)
Vo výstupe vidíme, že oba prístupy dávajú rovnaký výsledok:
Konzolová aplikácia
[2, 4, 6, 8]
[2, 4, 6, 8]
Vytvoriť zoznam týmto spôsobom sa odporúča iba v jednoduchých prípadoch. Čím zložitejšiu zoznamovú komprehenciu vytvoríme, tým menej čitateľná bude a môže to byť kontraproduktívna:
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)
Vo výstupe opäť vidíme, že oba prístupy dávajú rovnaký výsledok:
Konzolová aplikácia
[1, 1, 1, 1]
[1, 1, 1, 1]
Množinové komprehencie
Vyššie uvedený spôsob je možné aplikovať aj na množiny. Postup je takmer identický, jediný rozdiel je v použitých zátvorkách. Pri množinách sa namiesto hranatých používajú zlož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)
Vo výstupe vidíme vygenerovanú množinu:
Konzolová aplikácia
{2, 4, 6, 8}
Množina v Pythone nezachováva poradie prvkov, takže aj keď sme vytvorili množinu s hodnotami v určitom poradí, Python ich môže vrátiť v ľubovoľnom poradí. Dôležité je, že všetky správne hodnoty sú zahrnuté v množine.
Slovníkové komprehencie
Komprehencie je možné použiť aj na tvorbu slovníkov. Zátvorky sa používajú rovnaké ako pri množinách. Rozdiel je ten, že pre každý prvok sa namiesto jedného výrazu zapisujú dva výrazy oddelené dvojbodkou. Prvý predstavuje kľúč, druhý hodnotu. Nasledujúci kód ukazuje elegantný spôsob, ako vytvoriť kópiu slovníka, kde dôjde k prehodeniu kľúčov a hodnôt. Nutné dodať, že hodnoty zdrojového slovníka musia byť nemenného typu:
slovnik = {"Homer": "Simpson", "Barney": "Gumble"} inverzni_slovnik = {prijmeni: jmeno for jmeno, prijmeni in slovnik.items()} print(inverzni_slovnik)
Vo výstupe potom vidíme:
Konzolová aplikácia
{'Simpson': 'Homer', 'Gumble': 'Barney'}
Pri hlbšom zamyslení je zrejmé, že tu chýba možnosť n-ticových
komprehencií. Pre nich by sa ponúkal zápis pomocou okrúhlych zátvoriek.
N-ticové komprehencie v Pythone neexistujú. Toto obmedzenie
sa však dá obísť vytvorením zoznamovej komprehencie a použitím vstavanej
funkcie tuple()
. Kód pre n-tici párnych čísel do 20 potom
vyzerá takto:
ntice = tuple([x for x in range(2, 21, 2)]) print(ntice)
Vo výstupe potom vidíme:
Konzolová aplikácia
(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
Lambda výrazy
Lambda výraz vracia funkčný objekt (je to teda iný
spôsob tvorby funkcie). Vytvoríme ho kľúčovým slovom lambda
.
Nasledujú nepovinné parametre oddelené čiarkou a potom dvojbodka, za ktorou
sa píše príslušný výraz. Ten môžeme prirovnať k telu klasickej
funkcie.
Oproti klasickým funkciám sa líšia nasledovne:
- Výsledná funkcia nemá meno (preto sa niekedy nazýva anonymný).
- Na definovanie takejto anonymnej funkcie nám musí stačiť jeden riadok (presnejšie jeden výraz).
Pozrime sa na prí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)
Vo výstupe vidíme, že výsledok je v oboch prípadoch rovnaký:
Konzolová aplikácia
7
7
Funkcie
Teraz si predstavíme niekoľko vstavaných funkcií pre prácu s iterovateľnými objektmi, ktoré v prípade vhodného použitia dokážu znateľne uľahčiť prácu.
enumerate()
Funkcia vytvorí dvojice, kde prvou položkou je index a druhou položkou je príslušný prvok zadaného iterovateľné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)
Vo výstupe vidíme:
Konzolová aplikácia
1 jaro
2 leto
3 podzim
4 zima
zip()
Funkcia vytvorí n-tice zo zadaných iterovateľných objektov tak, že každá n-tica obsahuje po jednom prvku z každého iterovateľné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
vo výstupe nejdú,
pretože im chýba zodpovedajúci prvok z objektu roky
:
Konzolová aplikácia
('Terminator', 'Schwarzenegger', 1984)
('Rambo', 'Stallone', 1982)
('Poslední skaut', 'Willis', 1991)
map()
Funkcia prijíma ako svoj argument inú funkciu, ktorú aplikuje na iterovateľné objekty tak, že za parametre tejto funkcie dosadia paralelne jednotlivé prvky príslušných iterovateľných objektov. To znamená, že počet parametrov funkcie sa musí rovnať počtu iterovateľných objektov.
Príklad jednoparametrickej funkcie so zoznamom:
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ýstupom je:
Konzolová aplikácia
25 Kc
1500 Kc
10000 Kc
500000 Kc
Príklad dvojparametrickej anonymnej funkcie s dvoma n-ticami rôznej dĺžky:
for soucet in map(lambda x, y: x + y, (1, 2, 3, 4), (100, 200, 300)): print(soucet)
Vo výstupe vidíme:
Konzolová aplikácia
101
202
303
filter()
Funkcia prijíma ako svoj argument inú funkciu, ktorú aplikuje na
iterovateľný objekt tak, že za parameter funkcie postupne dosadzuje
jednotlivé prvky tohto objektu. Ak funkcia s daným argumentom vráti
False
, prvok je zahodený:
cisla = [2, 5, 13, 16, 50, 55] licha_cisla = filter(lambda x: x % 2, cisla) print(list(licha_cisla))
Konzolová aplikácia
[5, 13, 55]
Kombinácia funkcií
map()
a filter()
V praxi sa často stretávame s obratmi, kde sa kombinujú výhody oboch spomínaných funkcií:
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))
Vo výstupe vidíme:
Konzolová aplikácia
['25Kc', '1500Kc']
Rovnaký účinok však docielime pomocou zoznamovej komprehencie nasledovne:
print([str(cena) + "Kc" for cena in ceny_v_kc if cena < 10_000])
V prípade funkcie map()
, ktorá pracuje s viacerými
iterovateľnými objektmi, si musíme ešte vypomôcť funkciou
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á aplikácia
[101, 202, 303]
[101, 202, 303]
Použitie zoznamovej komprehencie namiesto kombinovania funkcií sa všeobecne považuje za čistejšie riešenie. Kód je priamočiarejší a lepšie čitateľný.
V budúcej lekcii, ChainMap, NamedTuple a DeQue v Pythone , si vysvetlíme, na čo a ako sa v Pythone používajú kolekcie ChainMap, NamedTuple a DeQue.