7.3. Iterator

7.3.1. Rationale

  • EN: Iterator

  • PL: Iterator

  • Type: object

7.3.2. Use Cases

  • History (like browser history)

7.3.3. Problem

from dataclasses import dataclass, field


@dataclass
class BrowseHistory:
    _urls: list[str] = field(default_factory=list)

    def push(self, url: str) -> None:
        self._urls.append(url)

    def pop(self) -> str:
        self._urls.pop()

    def get_urls(self) -> list[str]:
        return self._urls


if __name__ == '__main__':
    history = BrowseHistory()
    history.push(url='https://a.example.com')
    history.push(url='https://b.example.com')
    history.push(url='https://c.example.com')

    for i in range(len(history.get_urls())):
        url = history.get_urls()[i]
        print(i)

7.3.4. Design

7.3.5. Implementation

../../_images/designpatterns-iterator-usecase.png
from dataclasses import dataclass, field


class Iterator:
    def has_next(self) -> bool:
        raise NotImplementedError

    def current(self) -> str:
        raise NotImplementedError

    def next(self) -> None:
        raise NotImplementedError


@dataclass
class BrowseHistory:
    _urls: list[str] = field(default_factory=list)

    def push(self, url: str) -> None:
        self._urls.append(url)

    def pop(self) -> str:
        self._urls.pop()

    def get_urls(self) -> list[str]:
        return self._urls

    def create_iterator(self) -> Iterator:
        return self.ListIterator(self)

    @dataclass
    class ListIterator(Iterator):
        __history: 'BrowseHistory'
        __index: int = 0

        def has_next(self) -> bool:
            return self.__index < len(history._urls)

        def current(self) -> str:
            return history._urls[self.__index]

        def next(self) -> None:
            self.__index += 1


if __name__ == '__main__':
    history = BrowseHistory()
    history.push(url='https://a.example.com')
    history.push(url='https://b.example.com')
    history.push(url='https://c.example.com')

    iterator = history.create_iterator()
    while iterator.has_next():
        url = iterator.current()
        print(url)
        iterator.next()
    # https://a.example.com
    # https://b.example.com
    # https://c.example.com

7.3.6. Pythonic

from dataclasses import dataclass, field


@dataclass
class Browser:
    history: list[str] = field(default_factory=list)

    def open(self, url: str) -> None:
        ...
        self.history.append(url)

    def __iter__(self) -> 'Browser':
        self._current = 0
        return self

    def __next__(self) -> str:
        if self._current >= len(self.history):
            raise StopIteration
        result = self.history[self._current]
        self._current += 1
        return result


if __name__ == '__main__':
    browser = Browser()
    browser.open('https://python.astrotech.io')
    browser.open('https://numpy.astrotech.io')
    browser.open('https://pandas.astrotech.io')
    browser.open('https://design-patterns.astrotech.io')

    for url in browser:
        print(url)

# https://python.astrotech.io
# https://numpy.astrotech.io
# https://pandas.astrotech.io
# https://design-patterns.astrotech.io
from dataclasses import dataclass, field


@dataclass
class Browser:
    history: list[str] = field(default_factory=list)

    def open(self, url: str) -> None:
        ...
        self.history.append(url)


if __name__ == '__main__':
    browser = Browser()
    browser.open('https://python.astrotech.io')
    browser.open('https://numpy.astrotech.io')
    browser.open('https://pandas.astrotech.io')
    browser.open('https://design-patterns.astrotech.io')

    for url in browser.history:
        print(url)

# https://python.astrotech.io
# https://numpy.astrotech.io
# https://pandas.astrotech.io
# https://design-patterns.astrotech.io

7.3.7. Assignments

Code 7.9. Solution
"""
* Assignment: DesignPatterns Behavioral Iterator
* Complexity: easy
* Lines of code: 9 lines
* Time: 8 min

English:
    1. Implement Iterator pattern
    2. Run doctests - all must succeed

Polish:
    1. Zaimplementuj wzorzec Iterator
    2. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> crew = Crew()
    >>> crew += 'Mark Watney'
    >>> crew += 'Jose Jimenez'
    >>> crew += 'Melissa Lewis'
    >>>
    >>> for member in crew:
    ...     print(member)
    Mark Watney
    Jose Jimenez
    Melissa Lewis
"""


class Crew:
    def __init__(self):
        self.members = list()

    def __iadd__(self, other):
        self.members.append(other)
        return self