Skip to content

Iterators and generators

Iterable[T] is anything a for loop can go over: lists, strings, dicts, sets, ranges, files and generators. An Iterator[T] is a single pass over one, which next advances:

let it = iter(["a", "b"])
print(next(it), next(it), next(it, None))
a b None

map, filter, zip and enumerate give iterators, computed as they’re read.

A function with yield is a generator. Its body runs a step at a time, as values are asked for, so it can describe a sequence that never ends:

def naturals(start: int) -> Iterator[int]:
var n = start
while True:
yield n
n += 1
def take[T](n: int, xs: Iterable[T]) -> list[T]:
var out: list[T] = []
for x in xs:
if len(out) == n:
break
out.append(x)
return out
let squares = (n * n for n in naturals(1))
print(take(5, squares))
print(take(3, filter(lambda n: n % 7 == 0, naturals(1))))
[1, 4, 9, 16, 25]
[7, 14, 21]

yield from yields everything another iterable does.

Declared as Generator[Y, S, R], a generator yields Ys, receives Ss through send, and returns an R:

def averager() -> Generator[float, float, int]:
var total = 0.0
var count = 0
var average = 0.0
while count < 3:
let value = yield average
total += value
count += 1
average = total / count
return count
let avg = averager()
next(avg)
print(avg.send(10.0), avg.send(20.0))
10.0 15.0

A class with __iter__ is iterable, and one with __next__ too is an iterator. Raise StopIteration to end it:

class Countdown:
n: int
def __init__(self, n: int):
self.n = n
def __iter__(self) -> Iterator[int]:
return self
def __next__(self) -> int:
if self.n == 0:
raise StopIteration()
self.n -= 1
return self.n + 1
print(list(Countdown(3)))
[3, 2, 1]

Next: Async.