yield turns a function into a generator function. When the function hits yield value, it: 1. Produces value to the caller, 2. Pauses local state (variables, instruction pointer), 3. Resumes from that point on the next next() / for iteration.
Unlike return, which exits once, a generator can yield many times.
def read_lines(path):
with open(path) as f:
for line in f:
yield line.rstrip("\n")
def pairwise(items):
it = iter(items)
prev = next(it)
for cur in it:
yield prev, cur
prev = cur
print(list(pairwise([1, 2, 3, 4])))
# [(1, 2), (2, 3), (3, 4)]
# yield from delegates to another iterable/generator
def flatten(nested):
for part in nested:
yield from part
print(list(flatten([[1, 2], [3]]))) # [1, 2, 3]`yield` vs `return` in one line: return hands back one final result; yield streams many intermediate results while keeping state alive.