Python - Iteratori


Iteratori u Pythonu

Iteratori su svugdje u Pythonu. Oni su elegantno implementovani u for petlje, comprehensions, generatore itd., ali su skriveni na vidiku. Iterator u Pythonu je jednostavno objekt koji se može iterirati. Objekt koji će vraćati podatke, jedan po jedan element. Tehnički gledano, Python objekt iteratora mora implementovati dvije posebne metode, __iter__() i __next__(), zajednički nazvane iterator protokol.

Objekt se naziva iterabilnim ako od njega možemo dobiti iteratora. Većina ugrađenih kontejnera u Pythonu, poput: list, tuple, string itd., su iterabilni. Funkcija iter() (koja zauzvrat poziva metodu __iter__()) vraća iteratora.



Iteriranje putem iteratora

Koristimo funkciju next() da ručno prelazimo kroz sve stavke iteratora. Kad dođemo do kraja i ne bude više podataka za vraćanje, podići će se izuzetak StopIteration. Pogledajmo primjer.

# definisanje listu
my_list = [4, 7, 0, 3]

# dobijanje iteratora koristeći iter()
my_iter = iter(my_list)

# prelistaj iterator koristeći next()

# Ispisuje: 4
print(next(my_iter))

# Ispisuje: 7
print(next(my_iter))

# next(obj) je isto što i obj.__next__()

# Ispisuje: 0
print(my_iter.__next__())

# Ispisuje: 3
print(my_iter.__next__())

# Ovo će dovesti do greške, jer nema preostalih predmeta
next(my_iter)

Elegantniji način automatskog ponavljanja je upotreba for petlje. Koristeći ovo, možemo prelaziti preko bilo kog objekta koji može vratiti iterator, na primjer: list, string, file itd.

>>> for element in my_list:
...     print(element)
...     
4
7
0
3


Rad for petlje za iteratore

Kao što vidimo u gornjem primjeru, petlja for je uspjela automatski prelistavati listu. Pogledajmo kako se for petlja zapravo implementuje u Pythonu.

for element in iterable:
    # uradi nešto sa elementom

Zapravo se implementuje kao:

# kreirajte objekt iteratora od te iterable-a
iter_obj = iter(iterable)

# beskonačna petlja
while True:
    try:
        # uzmi sljedeću stavku
        element = next(iter_obj)
        # uradi nešto sa elementom
    except StopIteration:
        # ako je StopIteration podignut, prekida se petlja
        break

Dakle, interno, petlja for kreira objekt iteratora, iter_obj, pozivanjem iter() na iterable-u. Ironično, ova for petlja je zapravo beskonačna while petlja.

Unutar petlje poziva next() da dobije sljedeći element i izvršava tijelo for petlje s ovom vrijednošću. Nakon što se svi predmeti isprazne, StopIteration se podiže, koji se iznutra hvata i petlja se završava. Imajte na umu da će proći bilo koja druga vrsta izuzetaka.



Izgradnja prilagođenih iteratora

Izgradnja iteratora od nule je jednostavna u Pythonu. Moramo samo implementovati __iter__() i __next__() metode. Metoda __iter__() vraća sam objekt iteratora. Ako je potrebno, može se izvršiti neka inicijalizacija.

Metoda __next__() mora vratiti sljedeću stavku u nizu. Po završetku i u narednim pozivima mora podići StopIteration. Ovdje prikazujemo primjer koji će nam dati sljedeći stepen 2 u svakoj iteraciji. Eksponent snage započinje od nule do broja korisnika. Ako ne znate ništa o objektno orijentiranom programiranju, pogledajte lekciju Python objektno orijentirano programiranje.

class PowTwo:
    """Klasa za implementaciju iteratora
     powers of two"""

    def __init__(self, max=0):
        self.max = max

    def __iter__(self):
        self.n = 0
        return self

    def __next__(self):
        if self.n <= self.max:
            result = 2 ** self.n
            self.n += 1
            return result
        else:
            raise StopIteration

# kreiranje objekta
numbers = PowTwo(3)

# stvaranje iterable od objekta
i = iter(numbers)

# Koristenje next da biste došli do sljedećeg elementa iteratora
print(next(i))
print(next(i))
print(next(i))
print(next(i))
print(next(i))

Takođe možemo koristiti for petlju za prelazak preko naše klase iteratora.

>>> for i in PowTwo(5):
...     print(i)
...     
1
2
4
8
16
32


Python beskonačni iteratori (Python Infinite Iterators)

Nije neophodno da se stavka u iteratorskom objektu mora iscrpiti. Može biti beskonačnih iteratora (koji nikad ne završavaju). Moramo biti oprezni pri rukovanju takvim iteratorima. Evo jednostavnog primjera za demonstraciju beskonačnih iteratora.

Ugrađena funkcija iter() može se pozvati s dva argumenta, pri čemu prvi argument mora biti objekt koji se može pozvati (funkcija), a drugi je stražar (sentinel). Iterator poziva ovu funkciju dok vraćena vrijednost ne bude jednaka stražaru (sentinelu).

>>> int()
0

>>> inf = iter(int,1)
>>> next(inf)
0
>>> next(inf)
0

Vidimo da funkcija int() uvijek vraća 0. Dakle, prosljeđivanje kao iter(int, 1) vratiće iterator koji poziva int() dok vraćena vrijednost ne bude jednaka 1. To se nikada ne događa i dobijamo beskonačni iterator. Takođe možemo izgraditi vlastite beskonačne iteratore. Sljedeći iterator će, teoretski, vratiti sve neparne brojeve.

class InfIter:
    """Beskonačni iterator za vraćanje svih
         neparni brojevi"""

    def __iter__(self):
        self.num = 1
        return self

    def __next__(self):
        num = self.num
        self.num += 2
        return num

Uzorak bi bio sljedeći.

>>> a = iter(InfIter())
>>> next(a)
1
>>> next(a)
3
>>> next(a)
5
>>> next(a)
7

I tako dalje...

Pazite da uključite završno stanje kada prelazite preko ovih vrsta beskonačnih iteratora. Prednost upotrebe iteratora je što štede resurse. Kao što je prikazano u prethodnom primjeru, mogli smo dobiti sve neparne brojeve bez pohrane cijelog brojevnog sistema u memoriju. Možemo imati beskonačne predmete (teoretski) u ograničenom pamćenju. Postoji lakši način za stvaranje iteratora u Pythonu. Da biste saznali više, posjetite lekciju Python generatori koji koriste yield.