Python Generators, yield and send

Let’s say you’re programming a scoring system for the local rounders team. In rounders, a batter can score a half-rounder or a full-rounder. It’s even possible for one batter to score a half-rounder and another to score a full-rounder off the same ball—for example, if a batter on base 3 and the hitting batter both make it round. In this scenario, we can’t just increment the score by 1 each time; that’d be too simple for rounders!

So, let’s craft a generator that increments by 1/2 by default but can also handle custom increments when we need them. Here’s the magic:

def score_tally():
    score = 0
    default = 0.5
    while True:
        incr = yield score
        score += incr if incr is not None else default

Line 5 is the star of the show—it’s what lets us use the send() method alongside the usual next() function. When we call send(value), that value gets passed to incr. When we use next(), yield score returns None. That’s why, on line 6, we check if incr is None: if it is, we add the default (0.5); otherwise, we add whatever was sent. Let’s see it in action:

>>> bonkers_batters = score_tally()
>>> next(bonkers_batters)
0
>>> next(bonkers_batters)
0.5
>>> next(bonkers_batters)
1
>>> next(bonkers_batters)
1.5
>>> bonkers_batters.send(1)
2.5
>>> bonkers_batters.send(1.5)
4
>>> next(bonkers_batters)
4.5

The Python Docs for Yield mention send, while the Python Docs for generator.send gives the bare-bones rundown. There’s also an async version, with asend(), although this one has to be awaited.

Until next time, happy coding.

Paul