7.1. Iterator¶
7.1.1. Rationale¶
Used for iterating in a
for
loop
7.1.2. Protocol¶
__iter__(self) -> self
__next__(self) -> raise StopIteration
iter(obj)
->obj.__iter__()
next(obj)
->obj.__next__()
class Iterator:
def __iter__(self):
self._current = 0
return self
def __next__(self):
if self._current >= len(self.values):
raise StopIteration
element = self.values[self._current]
self._current += 1
return element
7.1.3. Example¶
class Crew:
def __init__(self):
self.members = list()
def __iadd__(self, other):
self.members.append(other)
return self
def __iter__(self):
self._current = 0
return self
def __next__(self):
if self._current >= len(self.members):
raise StopIteration
result = self.members[self._current]
self._current += 1
return result
crew = Crew()
crew += 'Mark Watney'
crew += 'Jose Jimenez'
crew += 'Melissa Lewis'
for member in crew:
print(member)
# Mark Watney
# Jose Jimenez
# Melissa Lewis
7.1.4. Loop and Iterators¶
For loop:
DATA = [1, 2, 3]
for current in DATA:
print(current)
Intuitive implementation of the for
loop:
DATA = [1, 2, 3]
iterator = iter(DATA)
try:
current = next(iterator)
print(current)
current = next(iterator)
print(current)
current = next(iterator)
print(current)
current = next(iterator)
print(current)
except StopIteration:
pass
Intuitive implementation of the for
loop:
DATA = [1, 2, 3]
iterator = DATA.__iter__()
try:
current = iterator.__next__()
print(current)
current = iterator.__next__()
print(current)
current = iterator.__next__()
print(current)
current = iterator.__next__()
print(current)
except StopIteration:
pass
7.1.5. Built-in Type Iteration¶
Iterating str
:
for character in 'hello':
print(character)
# h
# e
# l
# l
# o
Iterating sequences:
for number in [1, 2, 3]:
print(number)
# 1
# 2
# 3
Iterating dict
:
DATA = {'a': 1, 'b': 2, 'c': 3}
for element in DATA:
print(element)
# a
# b
# c
Iterating dict
:
for key, value in DATA.items():
print(f'{key} -> {value}')
# a -> 1
# b -> 2
# c -> 3
Iterating nested sequences:
for key, value in [('a',1), ('b',2), ('c',3)]:
print(f'{key} -> {value}')
# a -> 1
# b -> 2
# c -> 3
7.1.6. Use Cases¶
Iterator implementation:
class Parking:
def __init__(self):
self._parked_cars = list()
def park(self, car):
self._parked_cars.append(car)
def __iter__(self):
self._current = 0
return self
def __next__(self):
if self._current >= len(self._parked_cars):
raise StopIteration
element = self._parked_cars[self._current]
self._current += 1
return element
parking = Parking()
parking.park('Mercedes')
parking.park('Maluch')
parking.park('Toyota')
for car in parking:
print(car)
# Mercedes
# Maluch
# Toyota
7.1.7. Standard Library Itertools¶
import itertools
itertools.count(start=0, step=1)
:
from itertools import count
data = count(3, 2)
next(data)
# 3
next(data)
# 5
next(data)
# 7
itertools.cycle(iterable)
:
from itertools import cycle
DATA = ['white', 'gray']
for color in cycle(DATA):
print(color)
# white
# gray
# white
# gray
# ...
itertools.cycle(iterable)
:
from itertools import cycle
DATA = ['even', 'odd']
for i, status in enumerate(cycle(DATA)):
print(i, status)
# 0, even
# 1, odd
# 2, even
# 3, odd
# ...
itertools.repeat(object[, times])
:
from itertools import repeat
data = repeat(10, 3)
data
# repeat(10, 3)
next(data)
# 10
next(data)
# 10
next(data)
# 10
next(data)
# Traceback (most recent call last):
# StopIteration
itertools.accumulate(iterable[, func, *, initial=None])
:
from itertools import accumulate
data = accumulate([1, 2, 3, 4])
next(data)
# 1
next(data)
# 3
next(data)
# 6
next(data)
# 10
next(data)
# Traceback (most recent call last):
# StopIteration
itertools.chain(*iterables)
:
from itertools import chain
keys = ['a', 'b', 'c']
values = [1, 2, 3]
for x in chain(keys, values):
print(x)
# a
# b
# c
# 1
# 2
# 3
itertools.chain(*iterables)
:
from itertools import chain
class Iterator:
def __iter__(self):
self._current = 0
return self
def __next__(self):
if self._current >= len(self.values):
raise StopIteration
element = self.values[self._current]
self._current += 1
return element
class Character(Iterator):
def __init__(self, *values):
self.values = values
class Number(Iterator):
def __init__(self, *values):
self.values = values
chars = Character('a', 'b', 'c')
nums = Number(1, 2, 3)
print(chain(chars, nums))
# <itertools.chain object at 0x116166970>
print(list(chain(chars, nums)))
# [1, 2, 3, 'a', 'b', 'c']
for x in chain(chars, nums):
print(x)
# a
# b
# c
# 1
# 2
# 3
itertools.compress(data, selectors)
:
from itertools import compress
data = compress('ABCDEF', [1,0,1,0,1,1])
next(data)
# 'A'
next(data)
# 'C'
next(data)
# 'E'
next(data)
# 'F'
next(data)
# Traceback (most recent call last):
# StopIteration
itertools.islice(iterable, start, stop[, step])
:
from itertools import islice
data = islice('ABCDEFG', 2, 6, 2 )
next(data)
# 'C'
next(data)
# 'E'
next(data)
# Traceback (most recent call last):
# StopIteration
itertools.starmap(function, iterable)
:
from itertools import starmap
data = starmap(pow, [(2,5), (3,2), (10,3)])
next(data)
# 32
next(data)
# 9
next(data)
# 1000
next(data)
# Traceback (most recent call last):
# StopIteration
itertools.product(*iterables, repeat=1)
:
from itertools import product
data = product(['a', 'b', 'c'], [1,2])
next(data)
# ('a', 1)
next(data)
# ('a', 2)
next(data)
# ('b', 1)
next(data)
# ('b', 2)
next(data)
# ('c', 1)
next(data)
# ('c', 2)
next(data)
# Traceback (most recent call last):
# StopIteration
itertools.permutations(iterable, r=None)
:
from itertools import permutations
data = permutations([1,2,3])
next(data)
# (1, 2, 3)
next(data)
# (1, 3, 2)
next(data)
# (2, 1, 3)
next(data)
# (2, 3, 1)
next(data)
# (3, 1, 2)
next(data)
# (3, 2, 1)
next(data)
# Traceback (most recent call last):
# StopIteration
itertools.combinations(iterable, r)
:
from itertools import combinations
data = combinations([1, 2, 3, 4], 2)
next(data)
# (1, 2)
next(data)
# (1, 3)
next(data)
# (1, 4)
next(data)
# (2, 3)
next(data)
# (2, 4)
next(data)
# (3, 4)
next(data)
# Traceback (most recent call last):
# StopIteration
itertools.combinations_with_replacement(iterable, r)
:
from itertools import combinations_with_replacement
data = combinations_with_replacement([1,2,3], 2)
next(data)
# (1, 1)
next(data)
# (1, 2)
next(data)
# (1, 3)
next(data)
# (2, 2)
next(data)
# (2, 3)
next(data)
# (3, 3)
next(data)
# Traceback (most recent call last):
# StopIteration
itertools.groupby(iterable, key=None)
. Make an iterator that returns consecutive keys and groups from the iterable. Generally, the iterable needs to already be sorted on the same key function. The operation of groupby() is similar to the uniq filter in Unix. It generates a break or new group every time the value of the key function changes. That behavior differs from SQL’s GROUP BY which aggregates common elements regardless of their input order:
from itertools import groupby
data = groupby('AAAABBBCCDAABBB')
next(data)
# ('A', <itertools._grouper object at 0x1215f5c70>)
next(data)
# ('B', <itertools._grouper object at 0x12157b4f0>)
next(data)
# ('C', <itertools._grouper object at 0x120e16ee0>)
next(data)
# ('D', <itertools._grouper object at 0x1215ef4c0>)
next(data)
# ('A', <itertools._grouper object at 0x12157b3a0>)
next(data)
# ('B', <itertools._grouper object at 0x12157b790>)
next(data)
# Traceback (most recent call last):
# StopIteration
[k for k, g in groupby('AAAABBBCCDAABBB')]
# A B C D A B
[list(g) for k, g in groupby('AAAABBBCCD')]
# AAAA BBB CC D
7.1.8. Assignments¶
"""
* Assignment: Protocol Iterator Implementation
* Complexity: easy
* Lines of code: 14 lines
* Time: 13 min
English:
1. Use data from "Given" section (see below)
2. Modify classes to implement iterator protocol
3. Iterator should return instances of `Mission`
4. All tests must pass
5. Compare result with "Tests" section (see below)
Polish:
1. Użyj danych z sekcji "Given" (patrz poniżej)
2. Zmodyfikuj klasy aby zaimplementować protokół iterator
3. Iterator powinien zwracać instancje `Mission`
4. Wszystkie testy muszą przejść
5. Porównaj wyniki z sekcją "Tests" (patrz poniżej)
Tests:
>>> from inspect import isclass, ismethod
>>> assert isclass(Astronaut)
>>> astro = Astronaut('Mark', 'Watney')
>>> assert hasattr(astro, 'firstname')
>>> assert hasattr(astro, 'lastname')
>>> assert hasattr(astro, 'missions')
>>> assert hasattr(astro, '__iter__')
>>> assert hasattr(astro, '__next__')
>>> assert ismethod(astro.__iter__)
>>> assert ismethod(astro.__next__)
>>> astro = Astronaut('Jan', 'Twardowski', missions=(
... Mission(1969, 'Apollo 11'),
... Mission(2024, 'Artemis 3'),
... Mission(2035, 'Ares 3'),
... ))
>>> for mission in astro:
... print(mission)
Mission(year=1969, name='Apollo 11')
Mission(year=2024, name='Artemis 3')
Mission(year=2035, name='Ares 3')
"""
# Given
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: 14 lines
* Time: 13 min
English:
1. Use data from "Given" section (see below)
2. Define class `Range` with parameters: `start`, `stop`, `step`
3. Write own implementation of a built-in `range(start, stop, step)` function
4. Assume, that user will never giv only one argument; always it will be either two or three arguments
5. Use Iterator protocol
6. All tests must pass
7. Compare result with "Tests" section (see below)
Polish:
1. Użyj danych z sekcji "Given" (patrz poniżej)
2. Zdefiniuj klasę `Range` z parametrami: `start`, `stop`, `step`
3. Zaimplementuj własne rozwiązanie wbudowanej funkcji `range(start, stop, step)`
4. Przyjmij, że użytkownik nigdy nie poda tylko jednego argumentu; zawsze będą to dwa lub trzy argumenty
5. Użyj protokołu Iterator
6. Wszystkie testy muszą przejść
7. Porównaj wyniki z sekcją "Tests" (patrz poniżej)
Tests:
>>> 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]
"""