Overview

Midori-san: "Heh heh. Today I'm gonna have fun making a generator. Wow, this feels pretty advanced."

def my_generator():
    yield "First string"
    yield "Next string"
    yield "Last string"

Midori-san: "Hmm... since this is advanced, maybe I should add type annotations too... well, maybe I’ll ask ChatGPT-chan for help (nervous laugh)."

from collections.abc import Generator

def my_generator() -> Generator[str, None, None]:
    yield "First string"
    yield "Next string"
    yield "Last string"

Midori-san: "Huh?! What’s with the None, None after str...?!"

It’s time for your daily Python lesson.

 

The place that explains those three types

A generator can be annotated using the generic type Generator[YieldType, SendType, ReturnType].

So, those three types are called YieldType, SendType, and ReturnType.

 

YieldType and ReturnType are pretty simple

  • YieldType is the type of values yielded by yield.
  • ReturnType is the type of the value returned by return (the value attached to StopIteration).
# NOTE: You'll often see 'from typing import Generator', but the docs recommend against that.
#       https://docs.python.org/ja/3/library/typing.html#typing.Generator
from collections.abc import Generator

# YieldType (the value yielded)
# ReturnType (the value returned)
# This is how you'd fill them in:
def my_generator() -> Generator[str, None, int]:
    yield "First string"
    yield "Next string"
    return 999  # This value becomes part of StopIteration when returned

gen = my_generator()
try:
    print(next(gen))  # --> "First string"
    print(next(gen))  # --> "Next string"
    print(next(gen))  # --> StopIteration(999)
except StopIteration as e:
    print(e.value)  # --> Value from 'return': 999

 

SendType is the tricky one

from collections.abc import Generator

# SendType (the value received via send)
def my_generator() -> Generator[str, int, None]:
    # First call next(), then the value sent in will become the return value of yield.
    received = yield "First string"
    yield str(received)

gen = my_generator()
print(next(gen))  # --> 'First string'
print(gen.send(123))  # --> '123'

First, call next(), then the value sent in will become the return value of yield.

For Midori-san, who thought yield was just for output, the phrase "return value of yield" itself is already confusing. And to make matters worse...

  • You can't call send(123) right from the start. (You generally can't go send → send.)
  • But you can start with send(None). (send(None) → send works.)

...These kinds of hidden rules make it even harder. Still, this is a nice piece of Python core knowledge to have picked up.

For the official documentation on send, check here:

 

The end

My first encounter with generators was about nine years ago.

Back then, I was overwhelmed just by yield itself. But now, I've gotten close enough to Generator-san that I can at least spar a little with that monster called send.