6.3. Iterator¶
EN: Iterator
PL: Iterator
Type: object
6.3.1. Pattern¶
History (like browser history)

6.3.2. 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:
return 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)
6.3.3. Solution¶

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:
return 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
6.3.4. Use Case - 0x01¶
from urllib.request import urlopen
from dataclasses import dataclass, field
from typing import Self
@dataclass
class Browser:
history: list[str] = field(default_factory=list)
def open(self, url: str) -> None:
self.history.append(url)
# return urlopen(url).read()
def __iter__(self) -> Self:
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://python3.info')
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://python3.info
# https://numpy.astrotech.io
# https://pandas.astrotech.io
# https://design-patterns.astrotech.io
6.3.5. Use Case - 0x02¶
from urllib.request import urlopen
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)
# return urlopen(url).read()
if __name__ == '__main__':
browser = Browser()
browser.open('https://python3.info')
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://python3.info
# https://numpy.astrotech.io
# https://pandas.astrotech.io
# https://design-patterns.astrotech.io
6.3.6. Assignments¶
"""
* Assignment: DesignPatterns Behavioral Iterator
* Complexity: easy
* Lines of code: 9 lines
* Time: 5 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
"""
* Assignment: Protocol Iterator Implementation
* Complexity: easy
* Lines of code: 9 lines
* Time: 5 min
English:
1. Modify classes to implement iterator protocol
2. Iterator should return instances of `Mission`
3. Run doctests - all must succeed
Polish:
1. Zmodyfikuj klasy aby zaimplementować protokół iterator
2. Iterator powinien zwracać instancje `Mission`
3. Uruchom doctesty - wszystkie muszą się powieść
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass, ismethod
>>> assert isclass(Astronaut)
>>> mark = Astronaut('Mark', 'Watney')
>>> assert hasattr(mark, 'firstname')
>>> assert hasattr(mark, 'lastname')
>>> assert hasattr(mark, 'missions')
>>> assert hasattr(mark, '__iter__')
>>> assert hasattr(mark, '__next__')
>>> assert ismethod(mark.__iter__)
>>> assert ismethod(mark.__next__)
>>> mark = Astronaut('Pan', 'Twardowski', missions=(
... Mission(1969, 'Apollo 11'),
... Mission(2024, 'Artemis 3'),
... Mission(2035, 'Ares 3'),
... ))
>>> for mission in mark:
... print(mission)
Mission(year=1969, name='Apollo 11')
Mission(year=2024, name='Artemis 3')
Mission(year=2035, name='Ares 3')
"""
from dataclasses import dataclass
@dataclass
class Astronaut:
firstname: str
lastname: str
missions: tuple = ()
@dataclass
class Mission:
year: int
name: str
"""
* Assignment: Protocol Iterator Range
* Complexity: medium
* Lines of code: 9 lines
* Time: 8 min
English:
1. Modify class `Range` to write own implementation
of a built-in `range(start, stop, step)` function
2. Assume, that user will never give only one argument;
it will always be either two or three arguments
3. Use Iterator protocol
4. Run doctests - all must succeed
Polish:
1. Zmodyfikuj klasę `Range` aby napisać własną implementację
wbudowanej funkcji `range(start, stop, step)`
2. Przyjmij, że użytkownik nigdy nie poda tylko jednego argumentu;
zawsze będą to dwa lub trzy argumenty
3. Użyj protokołu Iterator
4. Uruchom doctesty - wszystkie muszą się powieść
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass, ismethod
>>> assert isclass(Range)
>>> r = Range(0, 0, 0)
>>> assert hasattr(r, '__iter__')
>>> assert hasattr(r, '__next__')
>>> assert ismethod(r.__iter__)
>>> assert ismethod(r.__next__)
>>> list(Range(0, 10, 2))
[0, 2, 4, 6, 8]
>>> list(Range(0, 5))
[0, 1, 2, 3, 4]
"""
from dataclasses import dataclass
@dataclass
class Range:
start: int = 0
stop: int = None
step: int = 1