Generators (Reference)¶
This page specifies Generator[T], generator functions, generator expressions, the generator methods, and how a generator is consumed. Refusals are reported with INCAN-T0001.
Type¶
Generator[T] is the type of a lazy producer that yields values of type T.
A Generator[T] value is accepted by a for loop and by a parameter of type Iterable[T] or Iterator[T].
Generator functions¶
A function is a generator function when its body contains yield and its declared return type is Generator[T].
def numbers() -> Generator[int]:
yield 1
yield 2
Rules:
yield exprrequires the type ofexprto be assignable toTas a returned value's type is to a return type; a value of a narrower numeric type is widened toT(see Assignment between numeric types).- A bare
yield, without a value, is refused in a generator function. yieldis valid only in the body of a generator function or a fixture.- A bare
returnends the generator. return valueis refused in a generator function.- A function declared
-> Generator[T]without a reachableyieldis refused, unless it returns an existing generator value. - Calling a generator function does not run its body. The body runs when a consumer asks for the first item, and each
yieldsuspends it until the next item is asked for.
def words() -> Generator[str]:
yield "a" # accepted
yield # refused: a bare yield has no value
yield 1 # refused: 1 is not a str
def count() -> int:
yield 1 # refused: count is not a generator function
return 1
Returning an existing generator¶
An ordinary function may return an existing generator value without becoming a yield-based generator function.
def positives(xs: list[int]) -> Generator[int]:
return (x for x in xs if x > 0)
Generator expressions¶
A generator expression has the same clause shape as a comprehension and produces Generator[T].
(expr for binding in iterable if condition)
Rules:
exprdetermines the yielded element typeT.- Clauses run in source order.
- Each
forclause introduces bindings for later clauses and forexpr. - Each
ifclause must type-check asbool. - A generator expression may have several
forclauses and trailingiffilters. - A generator expression is lazy; a list comprehension builds its list when it is evaluated.
Methods¶
def map[U](self, f: (T) -> U) -> Generator[U]
def filter(self, f: (T) -> bool) -> Generator[T]
def take(self, n: int) -> Generator[T]
def collect(self) -> list[T]
map,filterandtakeare lazy: each returns a generator and produces no item until that generator is consumed.collectconsumes the generator.
Generator[T] satisfies Iterator[T]: a generator value has every iterator adapter and consumer of Collection protocols, such as flat_map, skip, enumerate, zip and batch. Terminal consumers, such as count, fold, reduce, any, all, find, for_each and sum, consume the generator.
Consumption¶
Advancing a generator resumes it until the next yielded value or until it finishes. Exhausting a generator ends iteration normally.
list(gen) and set(gen) consume a generator and collect the values it yields: list keeps every value in the order yielded, set keeps one of each distinct value.
def parities(limit: int) -> Generator[int]:
for value in range(limit):
yield value % 2
def main() -> None:
every: list[int] = list(parities(4)) # accepted
distinct: set[int] = set(parities(4)) # accepted