Lekce 4 - Iterátory v Pythonu
V předešlém cvičení, Řešené úlohy k 2.-3. lekci kolekcí v Pythonu, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
V tomto tutoriálu kolekcí v Pythonu se zaměříme na iterovatelné objekty a jejich důležitou podmnožinu - iterátory.
Iterovatelný objekt
Pomocí iterace můžeme postupně získat prvky uložené v nějaké
kolekci (například aplikací for
cyklu na seznam).
Iterovatelný objekt je potom takový objekt, na kterém lze provést
iteraci. Prvky získáme buď v pevně stanoveném pořadí (seznam)
nebo v pořadí náhodném (množina).
Iterátory v Pythonu nám poskytují výhodnou možnost postupně
zpracovávat velká množství dat, což je zvláště užitečné při práci s
velkými soubory nebo datovými proudy. Tyto nástroje nám umožňují
efektivněji organizovat naše kódy tím, že umožňují použití
smyčky for
s různými iterovatelnými objekty, jako jsou
seznamy, množiny, slovníky a soubory, přičemž je důležité si uvědomit,
že při jejich použití v smyčce for Python automaticky vytváří iterátory
z těchto iterovatelných struktur. Dále iterátory podporují koncept
líného vyhodnocování, což znamená, že prvky jsou
generovány a zpracovávány pouze v okamžiku, kdy jsou skutečně potřeba,
což v některých případech výrazně zlepšuje výkon. V této lekci se
podrobněji seznámíme s iterátory v Pythonu, naučíme se, jak je správně
vytvářet a používat pro efektivní zpracování dat a prozkoumáme různé
scénáře jejich praktického využití.
Iterátor
Iterátor je zodpovědný za iteraci na iterovatelném objektu. Pamatuje si, které prvky již poskytl (neposkytne jeden prvek dvakrát). Ve chvíli, kdy už není další prvek k dispozici, nám to oznámí.
V tento okamžik jeho úloha v programu končí, iterátor je tzv. vyčerpaný (exhausted). Pokud chceme znovu iterovat, musíme vytvořit nový iterátor.
Třída iterovatelného objektu musí implementovat
speciální metodu __iter__()
, která po zavolání vytvoří a
vrátí novou instanci třídy Iterator
(vytvoří nový
„nevyčerpaný“ iterátor). Metodu můžeme volat skrze vestavěnou funkci
iter()
.
Třída iterátoru pak musí implementovat speciální
metodu __iter__()
, která ale vrací odkaz na svoji instanci –
self
(nevytváří novou instanci). Zároveň musí mít
implementovanou metodu __next__()
, která po zavolání vrátí
další prvek z kolekce. Pokud již není další prvek k dispozici, vyvolá
StopIteration
výjimku. Metodu lze volat vestavěnou funkcí
next()
.
Je tedy velký rozdíl, jestli iterujeme na iterovatelném objektu (např.
seznamu), který zavoláním funkce iter()
vrací pokaždé nový
iterátor, nebo přímo na iterátoru, který vrací pouze sám
sebe.
Technicky je sice iterátor zároveň iterovatelný objekt (oba
implementují metodu __iter__()
). My je ale budeme v této lekci
rozlišovat a pokud budeme hovořit o iterovatelném objektu, budeme tím myslet
iterovatelný objekt, který není zároveň iterátorem.
Cyklus for
pod pokličkou
Než se vrhneme na praktické příklady, podívejme se ještě, jak funguje
cyklus for
, který je z hlediska iterace klíčový. Python totiž
ve skutečnosti aplikuje cyklus while
za pomoci následujícího
mechanismu:
seznam = [1, 2, 3, 4, 5] iterator = iter(seznam) try: while True: prvek = next(iterator) except StopIteration: pass
Na začátku zavolá funkci iter()
a obdrží iterátor. Poté
na něm opakovaně volá funkci next()
a získává jednotlivé
prvky dokud nenarazí na StopIteration
výjimku. To lze i
jednoduše ověřit. Vytvoříme si vlastní třídu Seznam
z
třídy list
a pouze jí lehce upravíme metodu
__iter__()
, abychom věděli, kdy byla volána:
class Seznam(list): def __iter__(self): print("Volána metoda __iter__()") return super().__iter__()
Nyní vytvoříme její instanci a použijeme for
cyklus:
s = Seznam([1, 2, 3]) for prvek in s: print(prvek)
Ve výstupu vidíme, že před vypisováním jednotlivých prvků je
zavolána metoda __iter__()
:
Konzolová aplikace
Volána metoda __iter__()
1
2
3
Vestavěné iterovatelné objekty a iterátory
Dosud probranou látku si nejprve vyzkoušíme na námi dobře známém
seznamu (objektu typu list
). Vytvoříme jeho instanci a
dvakrát po sobě necháme vypsat všechny jeho prvky:
horory = ["Vetřelec", "Frankenstein", "Věc"] for horor in horory: print(horor) for horor in horory: print(horor)
Vše proběhlo v pořádku, protože seznam je iterovatelný objekt:
Konzolová aplikace
Vetřelec
Frankenstein
Věc
Vetřelec
Frankenstein
Věc
Tuto skutečnost si zároveň můžeme ověřit například použitím
vestavěné funkce dir()
, která vrací seznam atributů
příslušného objektu:
print("__iter__" in dir(horory), "__next__" in dir(horory))
Ve výstupu vidíme:
Konzolová aplikace
(True, False)
Je tedy zřejmé, že třída list
má definovanou metodu
__iter__()
ale nikoliv metodu __next__()
. Zavoláním
funkce iter()
na náš seznam získáme jeho iterátor. Jelikož
seznam je iterovatelný objekt, měl by vrátit novou instanci
iterátoru, tedy nikoliv pouze odkaz na sebe:
print(iter(horory) is horory)
Ve výstupu vidíme:
Konzolová aplikace
False
Vše tedy probíhá dle očekávání. Nyní si znovu vytvoříme iterátor a
uložíme si odkaz na něj do proměnné. Můžeme volat funkci
next()
, popřípadě metodu __next__()
a získat
postupně jeho prvky:
iterator_hororu = iter(horory) print(next(iterator_hororu)) print(iterator_hororu.__next__())
Ve výstupu vidíme:
Konzolová aplikace
Vetřelec
Frankenstein
V tomtéž kódu můžeme dále použít i cyklus for
. Jen
musíme pamatovat na to, že iterátor se postupně
vyčerpává:
for horor in iterator_hororu: print(horor)
Výstup:
Konzolová aplikace
Věc
Jelikož jsme první dva prvky získali funkcí next()
,
for
cyklus nám vrátil pouze poslední prvek. Nyní je
iterátor vyčerpaný a pokud bychom chtěli znovu iterovat, museli
bychom buď iterovat na původním seznamu (který si příslušný iterátor
vytvoří sám automaticky) a nebo si iterátor znovu explicitně vytvořit sami
zavoláním funkce iter()
.
Python nabízí několik užitečných funkcí, které vracejí iterovatelné objekty nebo iterátory. Podívejme se na ně.
range()
Funkce range()
vrací objekt range
, což je
iterovatelný objekt:
r = range(5) print("__iter__" in dir(r), "__next__" in dir(r)) print(iter(r) is r)
Výstup:
Konzolová aplikace
True False
False
Z výpisu je zřejmé, že objekt range
má implementovanou
pouze metodu __iter__()
, která před každou iterací vytvoří
novou instanci iterátoru. Iterovat na objektu range
můžeme tím
pádem bez omezení:
print("První iterace:", end=" ") for cislo in r: print(cislo, end=", ") print("\n\nDalší iterace:", end=" ") for cislo in r: print(cislo, end=", ")
Ve výstupu vidíme:
Konzolová aplikace
První iterace: 0, 1, 2, 3, 4,
Další iterace: 0, 1, 2, 3, 4,
enumerate()
Oproti tomu funkce enumerate()
vrací objekt
enumerate
, což je iterátor. Tento objekt má
implementovanou jak metodu __iter__()
, tak metodu
__next__()
. Funkce iter()
vrací ten samý 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á aplikace
True True
True
Iterovat na objektu enumerate
můžeme maximálně
jednou. Funkce enumerate()
vytvoří dvojice, kde první
položkou je index a druhou položkou je příslušný prvek zadaného
iterovatelné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=", ")
Ve výstupu vidíme:
Konzolová aplikace
První iterace: (0, 'Homer'), (1, 'Vočko'), (2, 'Lenny'), (3, 'Carl'),
Další iterace:
Na závěr si ukažme tabulku nejčastěji používaných funkcí, které vrací iterovatelné objekty nebo iterátory:
Funkce vracející iterovatelný objekt | Funkce vracející iterátor |
---|---|
list() | enumerate() |
tuple() | zip() |
set() | map() |
dict() | filter() |
dict.keys() | open() |
dict.values() | |
dict.items() | |
range() |
To by k této lekci bylo vše.
V další lekci, Iterátory podruhé: Generátory v Pythonu, si vytvoříme vlastní iterátor, seznámíme se s generátory a prozkoumáme jejich výhody.