Historia pytania: Pojęcie iterowalności pojawiło się w Pythonie w celu ujednolicenia pracy z kolekcjami: listami, słownikami, zbiorami itp. Obiekt, po którym można przejść za pomocą pętli for, uznawany jest za iterowalny. Jest to realizowane za pomocą określonych metod magicznych.
Problem: Python wymaga określonych protokołów do poprawnej pracy pętli i funkcji związanych z sekwencjami. Jeśli użytkownik niewłaściwie zaimplementuje te protokoły w swojej klasie, standardowe mechanizmy (for, list(), sum() itd.) nie będą działać lub będą się zachowywać w sposób nieprzewidywalny.
Rozwiązanie:
Iterowalnym obiektem jest każdy obiekt, który implementuje metodę __iter__. Iterator to ten, który ma metodę __next__ oraz __iter__, zwracającą self. Zwykle obiekt zwracany przez __iter__ jest iteratorem, ale nie jest to konieczne. Przykład:
class MyRange: def __init__(self, start, end): self.start = start self.end = end def __iter__(self): self.current = self.start return self def __next__(self): if self.current < self.end: val = self.current self.current += 1 return val raise StopIteration for x in MyRange(1, 4): print(x) # 1, 2, 3
Kluczowe cechy:
__iter__.__next__, jak i __iter__ (zwracający self).__iter__ efektywnie realizuje się, jeśli wymagane są niezależne przejścia przez kolekcję.Czy metoda next jest wymagana dla każdego obiektu iterowalnego?
Nie. Dla obiektu iterowalnego wymagana jest tylko __iter__, zwracająca iterator. __next__ implementuje tylko sam iterator. Na przykład, lista nie ma metody __next__, ale jest iterowalna: jej __iter__ zwraca instancję iteratora.
lst = [1, 2, 3] print(hasattr(lst, '__next__')) # False
Czy obiekt może być samodzielnym iteratorem?
Tak, jeśli implementuje obie metody — zarówno __iter__, jak i __next__.
Czy można stworzyć kilka iteratorów z niezależnym stanem dla jednej kolekcji?
Tak, jeśli __iter__ zwraca za każdym razem nowy obiekt iteratora.
class MyList: def __init__(self, data): self.data = data def __iter__(self): return iter(self.data)
__next__ bez __iter__ (lub odwrotnie).Negatywny przypadek: Klasa iteratora przechowuje stan (na przykład, aktualny indeks) na poziomie klasy, a nie instancji, co powoduje, że równoległe przejścia się ze sobą kolidują. Zalety:
Pozytywny przypadek:
Każdy iterator przechowuje swój stan w instancji utworzonej w __iter__.
Zalety: