4. diel - Iterátory v Pythone
V predchádzajúcom cvičení, Riešené úlohy k 2.-3. lekciu kolekcií v Pythone, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
V tomto tutoriále kolekcií v Pythone sa zameriame na iterovateľné objekty a ich dôležitú podmnožinu- iterátory.
Iterovateľný objekt
Pomocou iterácie môžeme postupne získať prvky uložené v
nejakej kolekcii (napríklad aplikácií for
cyklu na
zoznam). Iterovateľný objekt je potom taký objekt, na ktorom je
možné vykonať iteráciu. Prvky získame buď v pevne stanovenom
poradí (zoznam) alebo v poradí náhodnom (množina).
Iterátory v Pythone nám poskytujú výhodnú možnosť postupne
spracovávať veľké množstvo dát, čo je obzvlášť užitočné pri práci
s veľkými súbormi alebo dátovými prúdmi. Tieto nástroje nám umožňujú
efektívnejšie organizovať naše kódy tým, že umožňujú použitie
slučky for
s rôznymi iterovateľnými objektmi, ako sú
zoznamy, množiny, slovníky a súbory, pričom je dôležité si uvedomiť, že
pri ich použití v slučke for Python automaticky vytvára iterátory z týchto
iterovateľných štruktúr. Ďalej iterátory podporujú koncept
lenivého vyhodnocovania, čo znamená, že prvky sú
generované a spracovávané iba v okamihu, keď sú skutočne potrebné, čo v
niektorých prípadoch výrazne zlepšuje výkon. V tejto lekcii sa
podrobnejšie zoznámime s iterátormi v Pythone, naučíme sa, ako ich správne
vytvárať a používať na efektívne spracovanie dát a preskúmame rôzne
scenáre ich praktického využitia.
Iterátor
Iterátor je zodpovedný za iteráciu na iterovateľnom objekte. Pamätá si, ktoré prvky už poskytol (neposkytne jeden prvok dvakrát). Vo chvíli, keď už nie je ďalší prvok k dispozícii, nám to oznámi.
V tomto okamihu jeho úloha v programe končí, iterátor je tzv. vyčerpaný (exhausted). Ak chceme znova iterovať, musíme vytvoriť nový iterátor.
Trieda iterovateľného objektu musí implementovať
špeciálnu metódu __iter__()
, ktorá po zavolaní vytvorí a
vráti novú inštanciu triedy Iterator
(vytvorí nový
„nevyčerpaný“ iterátor). Metódu môžeme volať cez vstavanú funkciu
iter()
.
Trieda iterátora potom musí implementovať špeciálnu
metódu __iter__()
, ktorá ale vracia odkaz na svoju inštanciu –
self
(nevytvára novú inštanciu). Zároveň musí mať
implementovanú metódu __next__()
, ktorá po zavolaní vráti
ďalší prvok z kolekcie. Pokiaľ už nie je ďalší prvok k dispozícii,
vyvolá StopIteration
výnimku. Metódu je možné volať vstavanou
funkciou next()
.
Je teda veľký rozdiel, či iterujeme na iterovateľnom objekte (napr.
zozname), ktorý zavolaním funkcie iter()
vracia zakaždým nový
iterátor, alebo priamo na iterátore, ktorý vracia iba sám
seba.
Technicky je síce iterátor zároveň iterovateľný objekt
(obaja implementujú metódu __iter__()
). My ich ale budeme v tejto
lekcii rozlišovať a pokiaľ budeme hovoriť o iterovateľnom objekte, budeme
tým myslieť iterovateľný objekt, ktorý nie je zároveň iterátorom.
Cyklus for
pod pokrievkou
Než sa vrhneme na praktické príklady, pozrime sa ešte, ako funguje cyklus
for
, ktorý je z hľadiska iterácie kľúčový. Python totiž v
skutočnosti aplikuje cyklus while
pomocou nasledujúceho
mechanizmu:
seznam = [1, 2, 3, 4, 5] iterator = iter(seznam) try: while True: prvek = next(iterator) except StopIteration: pass
Na začiatku zavolá funkciu iter()
a dostane iterátor. Potom
na ňom opakovane volá funkciu next()
a získava jednotlivé prvky
pokiaľ nenarazí na StopIteration
výnimku. To sa dá aj
jednoducho overiť. Vytvoríme si vlastnú triedu Seznam
z triedy
list
a iba jej ľahko upravíme metódu __iter__()
,
aby sme vedeli, kedy bola volaná:
class Seznam(list): def __iter__(self): print("Volána metoda __iter__()") return super().__iter__()
Teraz vytvoríme jej inštanciu a použijeme for
cyklus:
s = Seznam([1, 2, 3]) for prvek in s: print(prvek)
Vo výstupe vidíme, že pred vypisovaním jednotlivých prvkov je zavolaná
metóda __iter__()
:
Konzolová aplikácia
Volána metoda __iter__()
1
2
3
Vstavané iterovateľné objekty a iterátory
Doteraz prebranú látku si najskôr vyskúšame na nami dobre známom
zozname (objekte typu list
). Vytvoríme jeho inštanciu a
dvakrát po sebe necháme vypísať všetky jeho prvky:
horory = ["Vetřelec", "Frankenstein", "Věc"] for horor in horory: print(horor) for horor in horory: print(horor)
Všetko prebehlo v poriadku, pretože zoznam je iterovateľný objekt:
Konzolová aplikácia
Vetřelec
Frankenstein
Věc
Vetřelec
Frankenstein
Věc
Túto skutočnosť si zároveň môžeme overiť napríklad použitím
vstavanej funkcie dir()
, ktorá vracia zoznam atribútov
príslušného objektu:
print("__iter__" in dir(horory), "__next__" in dir(horory))
Vo výstupe vidíme:
Konzolová aplikácia
(True, False)
Je teda zrejmé, že trieda list
má definovanú metódu
__iter__()
ale nie metódu __next__()
. Zavolaním
funkcie iter()
na náš zoznam získame jeho iterátor. Keďže
zoznam je iterovateľný objekt, mal by vrátiť novú inštanciu
iterátora, teda nie len odkaz na seba:
print(iter(horory) is horory)
Vo výstupe vidíme:
Konzolová aplikácia
False
Všetko teda prebieha podľa očakávania. Teraz si znova vytvoríme
iterátor a uložíme si odkaz naň do premennej. Môžeme volať funkciu
next()
, poprípade metódu __next__()
a získať
postupne jeho prvky:
iterator_hororu = iter(horory) print(next(iterator_hororu)) print(iterator_hororu.__next__())
Vo výstupe vidíme:
Konzolová aplikácia
Vetřelec
Frankenstein
V tom istom kóde môžeme ďalej použiť aj cyklus for
. Len
musíme pamätať na to, že iterátor sa postupne
vyčerpáva:
for horor in iterator_hororu: print(horor)
Výstup:
Konzolová aplikácia
Věc
Keďže sme prvé dva prvky získali funkciou next()
,
for
cyklus nám vrátil iba posledný prvok. Teraz je
iterátor vyčerpaný a ak by sme chceli znova iterovať, museli by sme
buď iterovať na pôvodnom zozname (ktorý si príslušný iterátor vytvorí
sám automaticky) alebo si iterátor znova explicitne vytvoriť sami zavolaním
funkcie iter()
.
Python ponúka niekoľko užitočných funkcií, ktoré vracajú iterovateľné objekty alebo iterátory. Pozrime sa na ne.
range()
Funkcia range()
vracia objekt range
, čo je
iterovateľný objekt:
r = range(5) print("__iter__" in dir(r), "__next__" in dir(r)) print(iter(r) is r)
Výstup:
Konzolová aplikácia
True False
False
Z výpisu je zrejmé, že objekt range
má implementovanú iba
metódu __iter__()
, ktorá pred každou iteráciou vytvorí novú
inštanciu iterátora. Iterovať na objekte range
môžeme tým
pádom bez obmedzenia:
print("První iterace:", end=" ") for cislo in r: print(cislo, end=", ") print("\n\nDalší iterace:", end=" ") for cislo in r: print(cislo, end=", ")
Vo výstupe vidíme:
Konzolová aplikácia
První iterace: 0, 1, 2, 3, 4,
Další iterace: 0, 1, 2, 3, 4,
enumerate()
Oproti tomu funkcia enumerate()
vracia objekt
enumerate
, čo je iterátor. Tento objekt má
implementovanú ako metódu __iter__()
, tak metódu
__next__()
. Funkcia iter()
vracia ten istý
objekt:
e = enumerate(["Homer", "Vočko", "Lenny", "Carl"]) print("__iter__" in dir(e), "__next__" in dir(e)) print(iter(e) is e)
Výstup:
Konzolová aplikácia
True True
True
Iterovať na objekte enumerate
môžeme maximálne
raz. Funkcia enumerate()
vytvorí dvojice, kde prvou
položkou je index a druhou položkou je príslušný prvok zadaného
iterovateľného objektu:
e = enumerate(["Homer", "Vočko", "Lenny", "Carl"]) print("První iterace:", end=" ") for postava in e: print(postava, end=", ") print("\n\nDalší iterace:", end=" ") for postava in e: print(postava, end=", ")
Vo výstupe vidíme:
Konzolová aplikácia
První iterace: (0, 'Homer'), (1, 'Vočko'), (2, 'Lenny'), (3, 'Carl'),
Další iterace:
Na záver si ukážme tabuľku najčastejšie používaných funkcií, ktoré vracajú iterovateľné objekty alebo iterátory:
Funkcia vracajúci iterovateľný objekt | Funkcia vracajúci iterátor |
---|---|
list() | enumerate() |
tuple() | zips() |
set() | máp() |
dict() | filter() |
dict.keys() | open() |
dict.values() | |
dict.items() | |
range() |
V ďalšej lekcii, Iterátory druhýkrát: Generátory v Pythone , si vytvoríme vlastný iterátor, zoznámime sa s generátormi a preskúmame ich výhody.