8.3. Iterator

8.3.1. Rationale

  • EN: Iterator

  • PL: Iterator

  • Type: object

8.3.2. Use Cases

  • History (like browser history)

8.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)

8.3.4. Design

8.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

8.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

8.3.7. Assignments