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.