Iteratory i *iterable* ------------------------------------- Zastanawialiście się kiedyś, dlaczego poniższy program działa: .. code-block:: python :linenos: lista = [ (i * 101) % 23 for i in range(20) ] for l in lista: print(l) Obiekt klasy ``list`` jest **iterable**, ponieważ potrafi zwrócić **iterator** do swojej zawartości. W Pythonie osiąga się to przez dodanie do klasy *magicznej* metody ``__iter__()``. Czym zatem jest iterator? **Iterator** to obiekt, który potrafi podać *następny obiekt w sekwencji*. W Pythonie osiąga się to przez dodanie do klasy *magicznej* metody ``__next__()``. .. literalinclude:: programy/iterate_by_2.py :language: python :linenos: A oto już zupełnie praktyczny przykład iteratora po zakresie liczb rzeczywistych: .. literalinclude:: programy/iterate_by_step.py :language: python :linenos: Kolejny przykład iteratora podaje wybrane elementy źródłowej listy wg zadanej kolejności. Konstruktor tego obiektu przyjmuje źródłową listę oraz dodatkowo listę indeksów, definiującą kolejność iteracji: .. literalinclude:: programy/CustomOrderIterator.py :language: python :linenos: W powyższych przykładach trzech przykładach klasy są zarówno *iterable* jak i iteratorami, co niestety często nie jest dobrym rozwiązaniem. Iterator bowiem jest obiektem, który musi pamiętać aktualny stan iteracji, tzn na czym stanęło ostatnie wywołanie metody ``__next__()``. Wywołanie ponownie ``__iter__()`` z tego samego obiektu co poprzednio przez zakończeniem poprzedniej iteracji psuje już istniejący iterator. Powyższe przykłady działały, bowiem każda pętla tworzyła nowy obiekt, np tu: .. literalinclude:: programy/iterate_by_step.py :language: python :lines: 22, 23 albo tu: .. literalinclude:: programy/iterate_by_2.py :language: python :lines: 18 Instrukcje ``EvenNumbers()`` oraz ``EvenlySpacedFloats(0.0,0.1,1.0)`` są wywołaniami konstruktorów a zatem tworzą nowe obiekty. Kiedy zatem to nie będzie działać? Problem ten ilustruje poniższy przykład. Uruchom go i przekonaj się, że podwójna iteracja jest błędna, choć pojedyncza działa prawidłowo. .. raw:: html
Przyjmimy, że obiekt ``VeryBigContainer`` zawiera dużo danych a jego konstrukcja jest kosztowna. Chcesz uniknąć tworzenia kilku jego kopii. Najpierw stworzysz obiekt tej klasy: .. literalinclude:: programy/iterate_over_existing.py :language: python :linenos: :lineno-start: 43 :lines: 43 zapakujesz do niego odpowiednie dane i rozpoczniesz iterację: .. literalinclude:: programy/iterate_over_existing.py :language: python :linenos: :lineno-start: 50 :lines: 50 Do tej pory wszystko działa zgodnie z założeniami. Problem pojawia się w podwójnie zagnieżdżonej iteracji: .. literalinclude:: programy/iterate_over_existing.py :language: python :linenos: :lineno-start: 54 :lines: 54-56 Fragmenty ``in data`` w linii 54 i w linii 55 odwołują się do tego samego obiektu ``data``. Instrukcja ``for el_j in data:`` powoduje wywołanie metody ``__next__()`` co zmienia wewnętrzny stan iteratora a tym samym wpływa na iterację po ``el_i``. W takiej sytuacji należy rozdzielić funkcjonalność iteratora od *iterable* pisząc dwie oddzielne klasy: jedną na pojemnik (czyli tu ``VeryBigContainer``) a drugą na iterator. .. raw:: html