NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!

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ě:

  1. Výsledná funkce nemá jméno (proto se někdy nazývá anonymní).
  2. 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.


 

Předchozí článek
Řešené úlohy k 4.-6. lekci kolekcí v Pythonu
Všechny články v sekci
Kolekce v Pythonu
Přeskočit článek
(nedoporučujeme)
ChainMap, NamedTuple a DeQue v Pythonu
Článek pro vás napsal synek.o
Avatar
Uživatelské hodnocení:
271 hlasů
Aktivity