Skip to main content

Objects/genobject.c (part 3)

Source:

cpython 3.14 @ ab2d84fe1023/Objects/genobject.c

This annotation covers exception injection and cleanup for generators and coroutines. See objects_generatorobject5_detail for gen_send_ex, frame resumption, and StopIteration propagation.

Map

LinesSymbolRole
1-80gen_throwInject an exception into a generator
81-160gen_closeFinalize a generator with GeneratorExit
161-260coro_throwCoroutine variant of throw
261-380Async generator cleanupaclose() and athrow()
381-500PyGen_SetStopIterationValueCapture the generator return value

Reading

gen_throw

// CPython: Objects/genobject.c:480 gen_throw
static PyObject *
gen_throw(PyGenObject *gen, PyObject *const *args, Py_ssize_t nargs)
{
/* gen.throw(exc) or gen.throw(type, value, traceback) */
PyObject *typ = args[0];
PyObject *val = nargs > 1 ? args[1] : NULL;
PyObject *tb = nargs > 2 ? args[2] : NULL;
if (PyExceptionInstance_Check(typ)) {
val = typ;
typ = PyExceptionInstance_Class(typ);
}
/* Check if the generator is already closed */
if (gen->gi_frame_state == FRAME_COMPLETED) {
PyErr_SetObject(typ, val);
return NULL;
}
PyObject *yf = _PyGen_yf(gen);
if (yf) {
/* Sub-generator: delegate throw to it */
...
}
/* Inject the exception into the generator's frame */
_PyErr_SetObject(tstate, typ, val);
return gen_send_ex(gen, Py_None, 1, 0);
}

generator.throw(exc) injects exc at the point where the generator is suspended. If the generator is delegating via yield from / await, the throw is recursively forwarded to the innermost sub-generator.

gen_close

// CPython: Objects/genobject.c:560 gen_close
static PyObject *
gen_close(PyGenObject *gen, PyObject *args)
{
PyObject *yf = _PyGen_yf(gen);
int err = 0;
if (yf) {
/* Close the sub-generator first */
err = gen_close_iter(yf);
}
if (err == 0) {
PyErr_SetNone(PyExc_GeneratorExit);
}
PyObject *retval = gen_send_ex(gen, Py_None, err != 0, 1);
if (retval) {
const char *msg = "generator ignored GeneratorExit";
if (PyCoro_CheckExact(gen)) msg = "coroutine ignored GeneratorExit";
Py_DECREF(retval);
PyErr_SetString(PyExc_RuntimeError, msg);
return NULL;
}
...
}

generator.close() throws GeneratorExit. If the generator catches it and yields again, RuntimeError("generator ignored GeneratorExit") is raised. This enforces the contract that generators must not suppress GeneratorExit.

PyGen_SetStopIterationValue

// CPython: Objects/genobject.c:80 PyGen_SetStopIterationValue
int
PyGen_SetStopIterationValue(PyObject *value)
{
/* Called when a generator executes 'return value'.
Stores value in the thread state so StopIteration(value)
can be raised by the caller. */
PyThreadState *tstate = _PyThreadState_GET();
if (value == NULL || value == Py_None) {
tstate->stopiteration_value = NULL;
} else {
tstate->stopiteration_value = Py_NewRef(value);
}
return 0;
}

return value inside a generator compiles to RETURN_VALUE. The interpreter calls PyGen_SetStopIterationValue(value) then sets the frame state to FRAME_COMPLETED. The caller's SEND/FOR_ITER opcode checks for this value to populate StopIteration.value.

gopy notes

gen_throw is objects.GeneratorThrow in objects/generator.go. Sub-generator delegation checks objects.IsGenerator. gen_close sends GeneratorExit via objects.GeneratorClose. PyGen_SetStopIterationValue stores the return value in vm.Thread.stopIterValue and is consumed by vm.evalSend.