Skip to content

Iterators & Generators

  • An iterable is any object you can loop over (list, str, dict, etc.).
  • An iterator is an object that implements __iter__() and __next__().
nums = [1, 2, 3]
it = iter(nums) # get iterator from iterable
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# next(it) # StopIteration
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
val = self.current
self.current -= 1
return val
for n in Countdown(3):
print(n) # 3, 2, 1

A generator function uses yield to produce values lazily — one at a time, on demand:

def countdown(n):
while n > 0:
yield n
n -= 1
for val in countdown(3):
print(val) # 3, 2, 1

Generators are memory-efficient — they don’t build the full sequence in memory.

A generator pauses at each yield and resumes from where it left off:

def gen():
print("start")
yield 1
print("middle")
yield 2
print("end")
g = gen()
next(g) # prints "start", returns 1
next(g) # prints "middle", returns 2
next(g) # prints "end", raises StopIteration

Delegates to a sub-generator:

def chain(*iterables):
for it in iterables:
yield from it
list(chain([1, 2], [3, 4])) # [1, 2, 3, 4]

Like list comprehensions but lazy:

# List comprehension — builds full list in memory
squares_list = [x**2 for x in range(1000000)]
# Generator expression — lazy, memory-efficient
squares_gen = (x**2 for x in range(1000000))
print(next(squares_gen)) # 0
print(sum(squares_gen)) # compute on the fly
def accumulator():
total = 0
while True:
value = yield total
if value is None:
break
total += value
acc = accumulator()
next(acc) # prime the generator
acc.send(10) # 10
acc.send(20) # 30
acc.send(5) # 35
# Reading large files line by line
def read_large_file(path):
with open(path) as f:
for line in f:
yield line.strip()
# Infinite sequence
def integers(start=0):
n = start
while True:
yield n
n += 1
from itertools import islice
first_10 = list(islice(integers(), 10))
import itertools
list(itertools.count(1, 2)) # 1, 3, 5, 7, ... (infinite)
list(itertools.cycle([1, 2, 3])) # 1, 2, 3, 1, 2, 3, ... (infinite)
list(itertools.repeat(5, 3)) # [5, 5, 5]
list(itertools.chain([1,2],[3,4])) # [1, 2, 3, 4]
list(itertools.islice(range(100), 5)) # [0, 1, 2, 3, 4]