Protokół iteratora w Pythonie
Protokół iteratora to nieformalna umowa, która definiuje, jak obiekt powinien zachowywać się w kontekście iteracji. Obiekt jest iterowalny (iterable), jeśli:
- Implementuje metodę
__iter__(), która zwraca iterator - Iterator implementuje metodę
__next__(), która zwraca kolejny element - Iterator rzuca
StopIteration, gdy nie ma więcej elementów
Jeśli chcesz poznać generatory (uproszczoną formę iteratorów), sprawdź Generatory i przetwarzanie danych.
Podstawowy iterator
Prosty iterator liczący od zera do określonej wartości:
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22class Counter: def __init__(self, max_value): self.max_value = max_value self.current = 0 def __iter__(self): """Zwraca iterator (w tym przypadku self)""" return self def __next__(self): """Zwraca kolejny element lub rzuca StopIteration""" if self.current < self.max_value: self.current += 1 return self.current - 1 else: raise StopIteration # Przykład użycia Counter counter = Counter(5) for value in counter: print(value) # Output: 0, 1, 2, 3, 4
Iterowalny vs Iterator
Ważne rozróżnienie:
- Iterowalny (Iterable) – obiekt, który może być iterowany (ma
__iter__()) - Iterator – obiekt, który faktycznie wykonuje iterację (ma
__next__())
Lista jest iterowalna, ale nie jest iteratorem:
Python1 2 3 4 5 6 7 8 9 10 11 12my_list = [1, 2, 3] # Lista jest iterowalna iter(my_list) # Działa - zwraca iterator # Lista nie jest iteratorem next(my_list) # TypeError: 'list' object is not an iterator # Iterator z listy iterator = iter(my_list) next(iterator) # 1 next(iterator) # 2
Rozdzielenie Iterable i Iterator
Często lepiej rozdzielić iterowalny obiekt od iteratora:
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35class NumberRange: """Iterowalny obiekt""" def __init__(self, start, end): self.start = start self.end = end def __iter__(self): """Zwraca nowy iterator""" return NumberRangeIterator(self.start, self.end) class NumberRangeIterator: """Iterator dla NumberRange""" def __init__(self, start, end): self.current = start self.end = end def __iter__(self): return self def __next__(self): if self.current < self.end: value = self.current self.current += 1 return value else: raise StopIteration # Przykład użycia NumberRange numbers = NumberRange(1, 4) for n in numbers: print(n) # 1, 2, 3 # Można iterować wielokrotnie (to działa!) for n in numbers: print(n) # 1, 2, 3 (znowu!)
Protokół sekwencji (Sequence Protocol)
Obiekty mogą być iterowalne przez implementację __getitem__() zamiast __iter__():
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26class SimpleRange: """Iterowalny przez protokół sekwencji""" def __init__(self, start, end): self.start = start self.end = end def __getitem__(self, index): """Pozwala na iterację przez pętlę for""" if index < 0: index += (self.end - self.start) if index < 0 or index >= (self.end - self.start): raise IndexError return self.start + index def __len__(self): return self.end - self.start # Przykład użycia SimpleRange r = SimpleRange(5, 10) for value in r: print(value) # 5, 6, 7, 8, 9 print(r[0]) # 5 print(r[4]) # 9
collections.abc – formalne interfejsy
collections.abc dostarcza abstrakcyjne klasy bazowe dla protokołów:
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34from collections.abc import Iterator, Iterable, Sequence class MyIterator(Iterator): """Iterator dziedziczący z Iterator""" def __init__(self, data): self.data = data self.index = 0 def __iter__(self): return self def __next__(self): if self.index >= len(self.data): raise StopIteration value = self.data[self.index] self.index += 1 return value class MySequence(Sequence): """Sekwencja dziedzicząca z Sequence""" def __init__(self, items): self.items = list(items) def __getitem__(self, index): return self.items[index] def __len__(self): return len(self.items) # Przykład użycia MySequence my_seq = MySequence([1, 2, 3, 4, 5]) print(list(my_seq)) # [1, 2, 3, 4, 5] print(len(my_seq)) # 5 print(my_seq[2]) # 3
Praktyczny przykład: Iterator dla pliku
Iterator, który czyta plik linia po linii bez ładowania całego pliku do pamięci:
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36class FileLineIterator: """Iterator do czytania pliku linia po linii""" def __init__(self, filename): self.filename = filename self.file = None def __iter__(self): self.file = open(self.filename, 'r', encoding='utf-8') return self def __next__(self): if self.file is None: raise StopIteration line = self.file.readline() if not line: self.file.close() self.file = None raise StopIteration return line.rstrip('\n') def __enter__(self): """Context manager""" return self def __exit__(self, exc_type, exc_val, exc_tb): """Zamyka plik przy wyjściu z context managera""" if self.file: self.file.close() return False # Przykład użycia FileLineIterator with FileLineIterator('data.txt') as lines: for line in lines: print(line)
Generator jako iterator
Generatory są najprostszą formą iteratorów:
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14def number_range(start, end): """Generator - automatycznie implementuje protokół iteratora""" current = start while current < end: yield current current += 1 # Generator jest iteratorem numbers = number_range(1, 4) print(hasattr(numbers, '__iter__')) # True print(hasattr(numbers, '__next__')) # True for n in numbers: print(n) # 1, 2, 3
Własne kolekcje iterowalne
Kompleksowy przykład: własna kolekcja z wieloma metodami:
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54from collections.abc import Iterable, Iterator class MyCollection: """Własna kolekcja z iteracją""" def __init__(self, items=None): self.items = list(items) if items else [] def __iter__(self): return MyCollectionIterator(self.items) def __len__(self): return len(self.items) def __getitem__(self, index): return self.items[index] def __setitem__(self, index, value): self.items[index] = value def append(self, item): self.items.append(item) def __repr__(self): return f"MyCollection({self.items})" class MyCollectionIterator(Iterator): def __init__(self, items): self.items = items self.index = 0 def __iter__(self): return self def __next__(self): if self.index >= len(self.items): raise StopIteration value = self.items[self.index] self.index += 1 return value # Przykład użycia MyCollection coll = MyCollection([1, 2, 3, 4, 5]) # Iteracja for item in coll: print(item) # List comprehension squared = [x**2 for x in coll] print(squared) # [1, 4, 9, 16, 25] # Funkcje wbudowane print(sum(coll)) # 15 print(max(coll)) # 5
Iteratory z warunkami
Iterator z warunkowym filtrowaniem:
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22class FilteredIterator: """Iterator filtrujący elementy""" def __init__(self, iterable, condition): self.iterator = iter(iterable) self.condition = condition def __iter__(self): return self def __next__(self): while True: value = next(self.iterator) if self.condition(value): return value # Jeśli warunek nie spełniony, kontynuuj szukanie # Przykład użycia FilteredIterator numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] even_numbers = FilteredIterator(numbers, lambda x: x % 2 == 0) for num in even_numbers: print(num) # 2, 4, 6, 8, 10
Najlepsze praktyki
1. Używaj generatorów, gdy to możliwe
Generatory są prostsze i bardziej Pythonic. Więcej o generatorach znajdziesz w artykule Generatory i przetwarzanie danych:
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20# ✅ Dobrze - generator def squares(n): for i in range(n): yield i ** 2 # ❌ Niepotrzebnie skomplikowane - klasa iteratora class Squares: def __init__(self, n): self.n = n self.current = 0 def __iter__(self): return self def __next__(self): if self.current >= self.n: raise StopIteration value = self.current ** 2 self.current += 1 return value
2. Rozdziel iterowalny od iteratora
Jeśli obiekt musi być iterowany wielokrotnie, rozdziel iterowalny od iteratora.
3. Używaj collections.abc
Dla formalnej definicji interfejsów użyj collections.abc. To część szerszego tematu protokołów w Pythonie, który omówiliśmy w Zaawansowane techniki w Pythonie.
4. Obsługuj edge cases
Zawsze obsługuj puste kolekcje i błędy.
Podsumowanie
Tworzenie własnych iteratorów pozwala na pełną kontrolę nad iteracją. Kluczowe koncepcje:
- ✅ Protokół iteratora –
__iter__()i__next__() - ✅ Protokół sekwencji –
__getitem__()dla iteracji - ✅ collections.abc – formalne interfejsy
- ✅ Generatory – najprostsza forma iteratorów
- ✅ Rozdzielenie – iterowalny vs iterator
Pamiętaj: używaj generatorów, gdy to możliwe, ale wiedz, jak tworzyć własne iteratory, gdy potrzebujesz więcej kontroli.
Co dalej?
Rozwijaj umiejętności zaawansowanego Pythona:
- Generatory i przetwarzanie danych – użycie generatorów w praktyce
- Zaawansowane techniki w Pythonie – więcej zaawansowanych koncepcji
- Nieskończone sekwencje w Pythonie – lazy evaluation z iteratorami
- Profilowanie wydajności w Pythonie – mierzenie wydajności iteratorów
Pamiętaj: protokoły to siła Pythona. Używaj ich, aby tworzyć kod zgodny z Pythonic idioms!



