Skip to main content

Objects/genobject.c (part 5)

Source:

cpython 3.14 @ ab2d84fe1023/Objects/genobject.c

This annotation covers the generator execution and lifecycle API. See objects_genobject4_detail for generator creation and __next__.

Map

LinesSymbolRole
1-100gen_send_ex2Core send/throw dispatcher
101-200generator.sendResume with a value
201-300generator.throwInject an exception
301-400generator.closeFinalize by throwing GeneratorExit
401-500gen_dealloc / finalizerGC and tp_finalize

Reading

gen_send_ex2

// CPython: Objects/genobject.c:180 gen_send_ex2
static PyObject *
gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject *exc, int closing)
{
PyThreadState *tstate = _PyThreadState_GET();
if (gen->gi_frame_state == FRAME_CREATED && arg != NULL && arg != Py_None) {
PyErr_SetString(PyExc_TypeError,
"can't send non-None value to a just-started generator");
return NULL;
}
if (gen->gi_frame_state >= FRAME_COMPLETED) {
if (!closing)
PyErr_SetNone(exc == NULL ? PyExc_StopIteration : PyExc_RuntimeError);
return NULL;
}
/* Resume the frame */
gen->gi_frame_state = FRAME_EXECUTING;
PyObject *result = _PyEval_EvalFrameDefault(tstate, frame, exc != NULL);
if (result == NULL) {
/* Generator raised StopIteration or another exception */
if (PyCoro_CheckExact(gen)) ...
}
return result;
}

gen_send_ex2 is the single entry point for all generator/coroutine resumption. arg is the value sent in (becomes the result of the yield expression). exc is non-NULL for throw. The FRAME_CREATED guard prevents send(non_None) on a fresh generator per the language spec.

generator.send

// CPython: Objects/genobject.c:260 gen_send
static PyObject *
gen_send(PyGenObject *gen, PyObject *arg)
{
return gen_send_ex2(gen, arg, NULL, 0);
}

g.send(value) resumes the generator and delivers value as the result of the yield. The first call must use send(None) or next(). Equivalent to next(g) when value is None.

generator.throw

// CPython: Objects/genobject.c:290 gen_throw
static PyObject *
gen_throw(PyGenObject *gen, PyObject *const *args, Py_ssize_t nargs)
{
/* throw(type[, value[, tb]]) or throw(exc_instance) */
PyObject *typ, *val = NULL, *tb = NULL;
if (nargs == 1 && PyExceptionInstance_Check(args[0])) {
val = args[0];
typ = PyExceptionInstance_Class(val);
} else {
typ = args[0];
if (nargs >= 2) val = args[1];
if (nargs >= 3) tb = args[2];
}
PyErr_Restore(typ, val, tb); /* set the exception state */
return gen_send_ex2(gen, NULL, val, 0);
}

g.throw(ValueError("bad")) injects an exception at the yield point. Inside the generator, a try/except can catch it and continue yielding. If not caught, the exception propagates to the caller.

generator.close

// CPython: Objects/genobject.c:360 gen_close
static PyObject *
gen_close(PyGenObject *gen, PyObject *args)
{
PyObject *retval;
PyObject *yf = _PyGen_yf(gen); /* yielded-from (for yield from) */
int err = 0;
if (yf) {
/* Close the inner generator first */
gen->gi_frame_state = FRAME_EXECUTING;
err = gen_close_iter(yf);
gen->gi_frame_state = FRAME_SUSPENDED;
Py_DECREF(yf);
}
if (err == 0)
PyErr_SetNone(PyExc_GeneratorExit);
retval = gen_send_ex2(gen, Py_None, NULL, 1);
if (retval) {
/* Generator yielded a value after GeneratorExit — error */
Py_DECREF(retval);
PyErr_SetString(PyExc_RuntimeError, "generator ignored GeneratorExit");
return NULL;
}
if (PyErr_ExceptionMatches(PyExc_StopIteration) ||
PyErr_ExceptionMatches(PyExc_GeneratorExit)) {
PyErr_Clear();
Py_RETURN_NONE;
}
return NULL;
}

g.close() throws GeneratorExit into the generator. The generator should either catch it and return (suppressed as StopIteration) or let it propagate. If the generator yields a value after seeing GeneratorExit, that is a RuntimeError. close() is called automatically by gen_dealloc via tp_finalize.

gen_dealloc

// CPython: Objects/genobject.c:100 gen_dealloc
static void
gen_dealloc(PyGenObject *gen)
{
if (gen->gi_frame_state != FRAME_COMPLETED) {
/* Still suspended: finalize via gen_close */
_PyObject_GC_UNTRACK(gen);
Py_INCREF(gen); /* revive during finalization */
gen_close_iter(gen);
Py_DECREF(gen);
}
PyObject_GC_Del(gen);
}

tp_finalize is set to call gen_close so that a generator deleted without being exhausted still has its finally blocks run. The Py_INCREF/Py_DECREF pair around gen_close_iter prevents the object from being freed while the frame is still running.

gopy notes

gen_send_ex2 maps to vm.GenSendEx in vm/eval_gen.go. generator.send is objects.GeneratorSend. generator.throw is objects.GeneratorThrow; it calls vm.SetException before resuming. generator.close is objects.GeneratorClose; it checks for objects.GeneratorExit and calls vm.GenClose on nested yield from. gen_dealloc corresponds to the Finalize method on objects.Generator.