7. diel - Komprehencie, lambda výrazy a funkcie v Pythone
V predchádzajúcom cvičení, Riešené úlohy k 4.-6. lekcii 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:
new_list = [element * 2 for element in another_list if element < 5]
.
Jednotlivé fázy teda sú:
- transformácia - výraz, ktorý definuje zmenu pôvodného
prvku (tu
element * 2
), - iterácia - transformácia sa postupne vykonáva na
jednotlivé prvky iterovateľného objektu (tu
for element in another_list
), - 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 element < 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:
source_list = [1, 2, 3, 4, 5] # for loop: new_list_1 = [] for element in source_list: if element < 5: new_list_1.append(element * 2) # list comprehension: new_list_2 = [element * 2 for element in source_list if element < 5] print(new_list_1) print(new_list_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:
matrix = [ [0, 1, 0], [1, 2, 1], [2, 0, 1], ] # list comprehension: ones = [number for row in matrix for number in row if number > 0 and number < 2] # less readable print(ones) # for loop: ones = [] for row in matrix: for number in row: if number > 0 and number < 2: ones.append(number) print(ones)
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é:
source_sequence = (1, 2, 3, 4, 5) double_generator = (element * 2 for element in source_sequence if element < 5) new_set = set(double_generator) print(new_set)
Skontroluj, či výstupy programu zodpovedajú predlohe. S inými textami testy neprejdú.
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:
dictionary = {"Homer": "Simpson", "Barney": "Gumble"} inverse_dictionary = {surname: name for name, surname in dictionary.items()} print(inverse_dictionary)
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:
tuple_example = tuple([x for x in range(2, 21, 2)]) print(tuple_example)
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).
Druhý bod sa dá využiť napr. pri funkciách vyššieho rádu, ktoré prijímajú ako argument inú funkciu. My sa na dve z nich pozrieme na konci lekcie.
Pozrime sa na príklad:
def normal_function(x, y): return x + y lambda_function = lambda x, y: x + y f1 = normal_function(2, 5) f2 = lambda_function(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:
seasons = ["spring", "summer", "autumn", "winter"] enum_seasons = enumerate(seasons, start=1) for index, season in enum_seasons: print(index, season)
Vo výstupe vidíme:
Konzolová aplikácia
1 spring
2 summer
3 autumn
4 winter
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:
movies = ['Terminator', 'Rambo', 'The Last Boy Scout', 'Titanic'] actors = ['Schwarzenegger', 'Stallone', 'Willis', 'Di Caprio'] years = [1984, 1982, 1991] may = zip(movies, actors, years) for i in may: print(i)
Film Titanic
a herec Di Caprio
vo výstupe nei sú,
pretože im chýba zodpovedajúci prvok z objektu years
:
Konzolová aplikácia
('Terminator', 'Schwarzenegger', 1984)
('Rambo', 'Stallone', 1982)
('The Last Boy Scout', '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:
prices_in_eur = [25, 1500, 10_000, 500_000] def format_price(price): return str(price) + ' €' prices = map(format_price, prices_in_eur) for p in prices: print(p)
Výstupom je:
Konzolová aplikácia
25 €
1500 €
10000 €
500000 €
Príklad dvojparametrickej anonymnej funkcie s dvoma n-ticami rôznej dĺžky:
for total in map(lambda x, y: x + y, (1, 2, 3, 4), (100, 200, 300)): print(total)
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ý:
numbers = [2, 5, 13, 16, 50, 55] odd_numbers = filter(lambda x: x % 2, numbers) print(list(odd_numbers))
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í:
prices_in_eur = [25, 1500, 10_000, 500_000] prices_below_10_000 = map(lambda price: str(price) + "€", filter(lambda price: price < 10_000, prices_in_eur)) print(list(prices_below_10_000))
Vo výstupe vidíme:
Konzolová aplikácia
['25€', '1500€']
Rovnaký účinok však docielime pomocou zoznamovej komprehencie nasledovne:
print([str(price) + "€" for price in prices_in_eur if price < 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.