Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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á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.


 

Předchozí článek
Řešené úlohy k 2.-3. lekci kolekcí v Pythonu
Všechny články v sekci
Kolekce v Pythonu
Přeskočit článek
(nedoporučujeme)
Iterátory podruhé: Generátory v Pythonu
Článek pro vás napsal gcx11
Avatar
Uživatelské hodnocení:
263 hlasů
(^_^)
Aktivity