Skip to content

Commit e81fe94

Browse files
authored
gh-119786: added InternalDocs/generators.md (#128524)
1 parent 3193cb5 commit e81fe94

File tree

2 files changed

+104
-6
lines changed

2 files changed

+104
-6
lines changed

Diff for: InternalDocs/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Runtime Objects
2525

2626
- [Code Objects](code_objects.md)
2727

28-
- [Generators (coming soon)](generators.md)
28+
- [Generators](generators.md)
2929

3030
- [Frames](frames.md)
3131

Diff for: InternalDocs/generators.md

+103-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,106 @@
1+
2+
Generators and Coroutines
3+
=========================
4+
15
Generators
2-
==========
6+
----------
7+
8+
Generators in CPython are implemented with the struct `PyGenObject`.
9+
They consist of a [`frame`](frames.md) and metadata about the generator's
10+
execution state.
11+
12+
A generator object resumes execution in its frame when its `send()`
13+
method is called. This is analogous to a function executing in its own
14+
fram when it is called, but a function returns to the calling frame only once,
15+
while a generator "returns" execution to the caller's frame every time
16+
it emits a new item with a
17+
[`yield` expression](https://docs.python.org/dev/reference/expressions.html#yield-expressions).
18+
This is implemented by the
19+
[`YIELD_VALUE`](https://docs.python.org/dev/library/dis.html#opcode-YIELD_VALUE)
20+
bytecode, which is similar to
21+
[`RETURN_VALUE`](https://docs.python.org/dev/library/dis.html#opcode-RETURN_VALUE)
22+
in the sense that it puts a value on the stack and returns execution to the
23+
calling frame, but it also needs to perform additional work to leave the generator
24+
frame in a state that allows it to be resumed. In particular, it updates the frame's
25+
instruction pointer and stores the interpreter's exception state on the generator
26+
object. When the generator is resumed, this exception state is copied back to the
27+
interpreter state.
28+
29+
The `frame` of a generator is embedded in the generator object struct as a
30+
[`_PyInterpreterFrame`](frames.md) (see `_PyGenObject_HEAD` in
31+
[`pycore_genobject.h`](../Include/internal/pycore_genobject.h)).
32+
This means that we can get the frame from the generator or the generator
33+
from the frame (see `_PyGen_GetGeneratorFromFrame` in the same file).
34+
Other fields of the generator struct include metadata (such as the name of
35+
the generator function) and runtime state information (such as whether its
36+
frame is executing, suspended, cleared, etc.).
37+
38+
Generator Object Creation and Destruction
39+
-----------------------------------------
40+
41+
The bytecode of a generator function begins with a
42+
[`RETURN_GENERATOR`](https://docs.python.org/dev/library/dis.html#opcode-RETURN_GENERATOR)
43+
instruction, which creates a generator object, including its embedded frame.
44+
The generator's frame is initialized as a copy of the frame in which
45+
`RETURN_GENERATOR` is executing, but its `owner` field is overwritten to indicate
46+
that it is owned by a generator. Finally, `RETURN_GENERATOR` pushes the new generator
47+
object to the stack and returns to the caller of the generator function (at
48+
which time its frame is destroyed). When the generator is next resumed by
49+
[`gen_send_ex2()`](../Objects/genobject.c), `_PyEval_EvalFrame()` is called
50+
to continue executing the generator function, in the frame that is embedded in
51+
the generator object.
52+
53+
When a generator object is destroyed in [`gen_dealloc`](../Objects/genobject.c),
54+
its embedded `_PyInterpreterFrame` field may need to be preserved, if it is exposed
55+
to Python as part of a [`PyFrameObject`](frames.md#frame-objects). This is detected
56+
in [`_PyFrame_ClearExceptCode`](../Python/frame.c) by the fact that the interpreter
57+
frame's `frame_obj` field is set, and the frame object it points to has refcount
58+
greater than 1. If so, the `take_ownership()` function is called to create a new
59+
copy of the interpreter frame and transfer ownership of it from the generator to
60+
the frame object.
61+
62+
Iteration
63+
---------
64+
65+
The [`FOR_ITER`](https://docs.python.org/dev/library/dis.html#opcode-FOR_ITER)
66+
instruction calls `__next__` on the iterator which is on the top of the stack,
67+
and pushes the result to the stack. It has [`specializations`](adaptive.md)
68+
for a few common iterator types, including `FOR_ITER_GEN`, for iterating over
69+
a generator. `FOR_ITER_GEN` bypasses the call to `__next__`, and instead
70+
directly pushes the generator stack and resumes its execution from the
71+
instruction that follows the last yield.
72+
73+
Chained Generators
74+
------------------
75+
76+
A `yield from` expression creates a generator that efficiently yields the
77+
sequence created by another generator. This is implemented with the
78+
[`SEND` instruction](https://docs.python.org/dev/library/dis.html#opcode-SEND),
79+
which pushes the value of its arg to the stack of the generator's frame, sets
80+
the exception state on this frame, and resumes execution of the chained generator.
81+
On return from `SEND`, the value at the top of the stack is sent back up
82+
the generator chain with a `YIELD_VALUE`. This sequence of `SEND` followed by
83+
`YIELD_VALUE` is repeated in a loop, until a `StopIteration` exception is
84+
raised to indicate that the generator has no more values to emit.
85+
86+
The [`CLEANUP_THROW`](https://docs.python.org/dev/library/dis.html#opcode-CLEANUP_THROW)
87+
instruction is used to handle exceptions raised from the send-yield loop.
88+
Exceptions of type `StopIteration` is handled, their `value` field hold the
89+
value to be returned by the generator's `close()` function. Any other
90+
exception is re-raised by `CLEANUP_THROW`.
91+
92+
Coroutines
93+
----------
394

4-
Coming soon.
95+
Coroutines are generators that use the value returned from a `yield` expression,
96+
i.e., the argument that was passed to the `.send()` call that resumed it after
97+
it yielded. This makes it possible for data to flow in both directions: from
98+
the generator to the caller via the argument of the `yield` expression, and
99+
from the caller to the generator via the send argument to the `send()` call.
100+
A `yield from` expression passes the `send` argument to the chained generator,
101+
so this data flow works along the chain (see `gen_send_ex2()` in
102+
[`genobject.c`](../Objects/genobject.c)).
5103

6-
<!--
7-
- Generators, async functions, async generators, and ``yield from`` (next, send, throw, close; and await; and how this code breaks the interpreter abstraction)
8-
-->
104+
Recall that a generator's `__next__` function simply calls `self.send(None)`,
105+
so all this works the same in generators and coroutines, but only coroutines
106+
use the value of the argument to `send`.

0 commit comments

Comments
 (0)