IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

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).
Poďme si všetko ukázať v praxi. Na porovnanie použijeme aj klasický cyklus 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:

  1. Výsledná funkcia nemá meno (preto sa niekedy nazýva anonymný).
  2. Na definovanie takejto anonymnej funkcie nám musí stačiť jeden riadok (presnejšie jeden výraz).
Druhý bod sa dá využiť napr. pri funkciách vyššieho poriadku, ktoré prijímajú ako argument inú funkciu. My sa na dve z nich pozrieme na konci lekcie.

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.


 

Predchádzajúci článok
Riešené úlohy k 4.-6. lekciu kolekcií v Pythone
Všetky články v sekcii
Kolekcia v Pythone
Preskočiť článok
(neodporúčame)
ChainMap, NamedTuple a DeQue v Pythone
Článok pre vás napísal synek.o
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Aktivity