Last modified: September 22, 2024
This article is written in: 馃嚨馃嚤
Iteratory
Iteratory to fundamentalny koncept w j臋zyku Python, szczeg贸lnie w kontek艣cie p臋tli i struktur iterowalnych, takich jak listy, krotki czy zbiory. Python traktuje iteratory jako narz臋dzie do sukcesywnego uzyskiwania kolejnych element贸w z kolekcji danych w spos贸b zorganizowany, z mo偶liwo艣ci膮 zatrzymania i wznowienia procesu iteracji. W tej rozbudowanej wersji om贸wimy, jak dzia艂aj膮 iteratory, jak je tworzy膰, oraz jak s膮 wykorzystywane przez r贸偶ne mechanizmy Pythonowe, takie jak p臋tla for
oraz generatory.
Poj臋cie iterowalno艣ci
W Pythonie obiekt jest iterowalny (ang. iterable), je艣li mo偶na po nim iterowa膰, tj. mo偶na sukcesywnie uzyskiwa膰 kolejne elementy. Obiekty te musz膮 implementowa膰 metod臋 __iter__()
, kt贸ra zwraca iterator. Iterator jest obiektem, kt贸ry realizuje protok贸艂 iteratora, to znaczy posiada metod臋 __next__()
, kt贸ra zwraca kolejny element w sekwencji.
Typowe struktury danych, takie jak listy, krotki, zbiory czy s艂owniki, s膮 iterowalne, co pozwala na u偶ycie p臋tli for
do iteracji nad ich elementami. W tle dzia艂a tutaj mechanizm oparty na iteratorach.
Iteracja przez list臋
for elem in [1, 2, 3]:
print(elem)
Powy偶sza p臋tla for
w rzeczywisto艣ci wywo艂uje na li艣cie metod臋 __iter__()
, kt贸ra zwraca iterator. Iterator ten posiada metod臋 __next__()
, kt贸ra jest wywo艂ywana w ka偶dej iteracji w celu uzyskania kolejnego elementu listy.
Przyk艂ad r臋cznego u偶ycia iteratora
Mo偶emy bezpo艣rednio pracowa膰 z iteratorem, korzystaj膮c z funkcji wbudowanej iter()
oraz metody next()
.
lista = [1, 2, 3]
iterator = iter(lista) # Tworzy iterator z listy
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3
# Kolejne wywo艂anie next()
wyrzuci wyj膮tek StopIteration
Kiedy iterator nie ma ju偶 kolejnych element贸w do zwr贸cenia, wywo艂anie next()
powoduje wygenerowanie wyj膮tku StopIteration
. Jest to sygna艂 dla p臋tli for
, aby zako艅czy膰 iteracj臋.
Protok贸艂 iteratora
Protok贸艂 iteratora w Pythonie sk艂ada si臋 z dw贸ch kluczowych metod:
__iter__()
: Zwraca obiekt iteratora, zazwyczaj zwracaself
.__next__()
: Zwraca kolejny element w sekwencji, a gdy element贸w brak, generuje wyj膮tekStopIteration
.
Zrozumienie tych metod jest kluczowe, je艣li chcemy definiowa膰 w艂asne iteratory.
Tworzenie w艂asnych iterator贸w
Aby stworzy膰 w艂asny iterator, konieczne jest zdefiniowanie klasy z implementacj膮 metod __iter__()
i __next__()
. Przyk艂ad klasy b臋d膮cej iteratorem:
class MojaKolekcja:
def __init__(self):
self.elementy = [1, 2, 3]
self.indeks = 0
def __iter__(self):
return self
def __next__(self):
if self.indeks < len(self.elementy):
wynik = self.elementy[self.indeks]
self.indeks += 1
return wynik
else:
raise StopIteration
kolekcja = MojaKolekcja()
for elem in kolekcja:
print(elem)
Dzia艂anie powy偶szego przyk艂adu:
- Metoda
__iter__()
zwraca referencj臋 do samego siebie, co oznacza, 偶e instancja klasy jest tak偶e swoim w艂asnym iteratorem. - Metoda
__next__()
zwraca kolejny element z listy, a gdy wszystkie elementy zostan膮 wyczerpane, rzuca wyj膮tekStopIteration
, ko艅cz膮c iteracj臋.
Teoretyczne uzasadnienie iterator贸w
Iteratory w Pythonie opieraj膮 si臋 na koncepcji leniwej ewaluacji (ang. lazy evaluation). W przeciwie艅stwie do standardowych kolekcji, kt贸re przechowuj膮 wszystkie swoje elementy w pami臋ci, iterator nie generuje element贸w z g贸ry, ale produkuje je na 偶膮danie. Pozwala to na efektywne wykorzystanie pami臋ci, zw艂aszcza w przypadku du偶ych kolekcji danych lub niesko艅czonych sekwencji.
Matematycznie iteratory mo偶na postrzega膰 jako funkcje, kt贸re na ka偶dym kroku zwracaj膮 kolejny element z pewnej sekwencji, bez potrzeby przechowywania jej pe艂nej reprezentacji w pami臋ci. Dzi臋ki temu mo偶liwa jest np. praca z niesko艅czonymi sekwencjami.
Generatory jako iteratory
Generatory s膮 specjalnym rodzajem iterator贸w, kt贸re s膮 definiowane za pomoc膮 funkcji wykorzystuj膮cych s艂owo kluczowe yield
. Generatory upraszczaj膮 proces tworzenia iterator贸w, automatycznie zarz膮dzaj膮c stanem iteracji i rzucaniem wyj膮tku StopIteration
po zako艅czeniu iteracji.
Przyk艂ad prostego generatora:
def generator_liczb():
for i in range(3):
yield i
for liczba in generator_liczb():
print(liczba)
Wyja艣nienie dzia艂ania generator贸w:
- Ka偶de wywo艂anie
yield
zatrzymuje wykonanie funkcji i zwraca warto艣膰. Wznowienie nast臋puje w miejscu, w kt贸rym funkcja zosta艂a przerwana. - Generator zachowuje sw贸j stan pomi臋dzy kolejnymi wywo艂aniami, co czyni go bardzo efektywnym przy implementacji z艂o偶onych iteracji.
Wyj膮tek StopIteration
w praktyce
Ka偶dy iterator po wyczerpaniu swoich element贸w rzuca wyj膮tek StopIteration
, kt贸ry informuje o zako艅czeniu iteracji. Jest to mechanizm kontrolny, kt贸ry pozwala p臋tlom for
automatycznie wykrywa膰, kiedy przesta膰 iterowa膰.
P臋tla for
automatycznie obs艂uguje wyj膮tek StopIteration
i przerywa iteracj臋, co czyni ten mechanizm intuicyjnym i 艂atwym w u偶yciu. Je艣li jednak korzystamy z iteracji r臋cznie za pomoc膮 next()
, musimy sami zadba膰 o odpowiedni膮 obs艂ug臋 wyj膮tku.
iterator = iter([1, 2, 3])
while True:
try:
element = next(iterator)
print(element)
except StopIteration:
break
Korzy艣ci z u偶ywania iterator贸w
- Iteratory s膮 efektywne pami臋ciowo, poniewa偶 generuj膮 elementy na 偶膮danie, a nie przechowuj膮 ca艂ej kolekcji w pami臋ci.
- Iteratory umo偶liwiaj膮 prac臋 z niesko艅czonymi strumieniami danych, generuj膮c elementy tylko wtedy, gdy s膮 potrzebne.
- Generatory pozwalaj膮 tworzy膰 zwi臋z艂e i czytelne iteratory bez konieczno艣ci pisania pe艂nych klas z metodami
__iter__()
i__next__()
.
Z艂o偶one generatory i przep艂yw danych
Generatory mo偶na r贸wnie偶 艂膮czy膰, tworz膮c bardziej z艂o偶one strumienie danych. Na przyk艂ad, mo偶na stworzy膰 generator, kt贸ry pobiera dane z innego generatora, przetwarza je i zwraca przetworzony wynik.
def podwoj_wartosci(generator):
for
wartosc in generator:
yield wartosc * 2
generator_liczb = (i for i in range(5))
podwojony_generator = podwoj_wartosci(generator_liczb)
for wartosc in podwojony_generator:
print(wartosc)
Powy偶szy przyk艂ad pokazuje, jak mo偶na tworzy膰 z艂o偶one przep艂ywy danych, gdzie generatory s膮 u偶ywane do transformacji danych w spos贸b p艂ynny i leniwy.