From f3335d37a1547a22d0c343025dc7cfc41c07070d Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Mon, 2 Feb 2026 18:07:17 -0500 Subject: [PATCH 1/6] gh-120321: Add gi_state, cr_state, and ag_state attributes Add `gi_state`, `cr_state`, and `ag_state` attributes to generators, coroutines, and async generators respectively. These attributes return the current state as a string (e.g., `GEN_RUNNING`, `CORO_SUSPENDED`). The `inspect.getgeneratorstate()`, `inspect.getcoroutinestate()`, and `inspect.getasyncgenstate()` functions now return these attributes directly. This is in preparation for making `gi_frame` thread-safe, which may involve stop-the-world synchronization. The new state attributes avoid potential performance cliffs in `inspect.getgeneratorstate()` and similar functions by not requiring frame access. --- Doc/library/inspect.rst | 19 ++++++ Include/internal/pycore_frame.h | 16 ++--- .../pycore_global_objects_fini_generated.h | 12 ++++ Include/internal/pycore_global_strings.h | 12 ++++ .../internal/pycore_runtime_init_generated.h | 12 ++++ .../internal/pycore_unicodeobject_generated.h | 48 ++++++++++++++ Lib/inspect.py | 24 +------ Lib/test/test_generators.py | 2 +- Lib/types.py | 6 ++ ...-02-02-17-50-14.gh-issue-120321.Xfr7tL.rst | 6 ++ Objects/genobject.c | 66 +++++++++++++++++++ Programs/test_frozenmain.h | 12 ++-- 12 files changed, 198 insertions(+), 37 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-02-02-17-50-14.gh-issue-120321.Xfr7tL.rst diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 5133f9f0c8e43b..3f19ad5d18842b 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -262,6 +262,12 @@ attributes (see :ref:`import-mod-attrs` for module attributes): | | | ``yield from``, or | | | | ``None`` | +-----------------+-------------------+---------------------------+ +| | gi_state | state of the generator, | +| | | one of ``GEN_CREATED``, | +| | | ``GEN_RUNNING``, | +| | | ``GEN_SUSPENDED``, or | +| | | ``GEN_CLOSED`` | ++-----------------+-------------------+---------------------------+ | async generator | __name__ | name | +-----------------+-------------------+---------------------------+ | | __qualname__ | qualified name | @@ -278,6 +284,13 @@ attributes (see :ref:`import-mod-attrs` for module attributes): +-----------------+-------------------+---------------------------+ | | ag_code | code | +-----------------+-------------------+---------------------------+ +| | ag_state | state of the async | +| | | generator, one of | +| | | ``AGEN_CREATED``, | +| | | ``AGEN_RUNNING``, | +| | | ``AGEN_SUSPENDED``, or | +| | | ``AGEN_CLOSED`` | ++-----------------+-------------------+---------------------------+ | coroutine | __name__ | name | +-----------------+-------------------+---------------------------+ | | __qualname__ | qualified name | @@ -298,6 +311,12 @@ attributes (see :ref:`import-mod-attrs` for module attributes): | | | created, or ``None``. See | | | | |coroutine-origin-link| | +-----------------+-------------------+---------------------------+ +| | cr_state | state of the coroutine, | +| | | one of ``CORO_CREATED``, | +| | | ``CORO_RUNNING``, | +| | | ``CORO_SUSPENDED``, or | +| | | ``CORO_CLOSED`` | ++-----------------+-------------------+---------------------------+ | builtin | __doc__ | documentation string | +-----------------+-------------------+---------------------------+ | | __name__ | original name of this | diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h index 50908f2cb7a1d2..5e73ae3c8549b8 100644 --- a/Include/internal/pycore_frame.h +++ b/Include/internal/pycore_frame.h @@ -44,18 +44,16 @@ extern PyFrameObject* _PyFrame_New_NoTrack(PyCodeObject *code); /* other API */ typedef enum _framestate { - FRAME_CREATED = -4, - FRAME_SUSPENDED = -3, - FRAME_SUSPENDED_YIELD_FROM = -2, - FRAME_SUSPENDED_YIELD_FROM_LOCKED = -1, - FRAME_EXECUTING = 0, - FRAME_COMPLETED = 1, - FRAME_CLEARED = 4 + FRAME_CREATED = 0, + FRAME_SUSPENDED = 1, + FRAME_SUSPENDED_YIELD_FROM = 2, + FRAME_SUSPENDED_YIELD_FROM_LOCKED = 3, + FRAME_EXECUTING = 4, + FRAME_CLEARED = 5 } PyFrameState; #define FRAME_STATE_SUSPENDED(S) ((S) >= FRAME_SUSPENDED && (S) <= FRAME_SUSPENDED_YIELD_FROM_LOCKED) -#define FRAME_STATE_FINISHED(S) ((S) >= FRAME_COMPLETED) - +#define FRAME_STATE_FINISHED(S) ((S) == FRAME_CLEARED) #ifdef __cplusplus } #endif diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index fc297a2933a786..cb62d9424a86b0 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1335,11 +1335,23 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_STR(str_replace_inf)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_STR(type_params)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_STR(utf_8)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(AGEN_CLOSED)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(AGEN_CREATED)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(AGEN_RUNNING)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(AGEN_SUSPENDED)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(CANCELLED)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(CORO_CLOSED)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(CORO_CREATED)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(CORO_RUNNING)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(CORO_SUSPENDED)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(Emax)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(Emin)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(FINISHED)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(False)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(GEN_CLOSED)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(GEN_CREATED)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(GEN_RUNNING)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(GEN_SUSPENDED)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(JSONDecodeError)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(PENDING)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(Py_Repr)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 563ccd7cf6d3f4..92de92079379ea 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -58,11 +58,23 @@ struct _Py_global_strings { } literals; struct { + STRUCT_FOR_ID(AGEN_CLOSED) + STRUCT_FOR_ID(AGEN_CREATED) + STRUCT_FOR_ID(AGEN_RUNNING) + STRUCT_FOR_ID(AGEN_SUSPENDED) STRUCT_FOR_ID(CANCELLED) + STRUCT_FOR_ID(CORO_CLOSED) + STRUCT_FOR_ID(CORO_CREATED) + STRUCT_FOR_ID(CORO_RUNNING) + STRUCT_FOR_ID(CORO_SUSPENDED) STRUCT_FOR_ID(Emax) STRUCT_FOR_ID(Emin) STRUCT_FOR_ID(FINISHED) STRUCT_FOR_ID(False) + STRUCT_FOR_ID(GEN_CLOSED) + STRUCT_FOR_ID(GEN_CREATED) + STRUCT_FOR_ID(GEN_RUNNING) + STRUCT_FOR_ID(GEN_SUSPENDED) STRUCT_FOR_ID(JSONDecodeError) STRUCT_FOR_ID(PENDING) STRUCT_FOR_ID(Py_Repr) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index ba7c0e68434517..dc05495e20d69d 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1333,11 +1333,23 @@ extern "C" { } #define _Py_str_identifiers_INIT { \ + INIT_ID(AGEN_CLOSED), \ + INIT_ID(AGEN_CREATED), \ + INIT_ID(AGEN_RUNNING), \ + INIT_ID(AGEN_SUSPENDED), \ INIT_ID(CANCELLED), \ + INIT_ID(CORO_CLOSED), \ + INIT_ID(CORO_CREATED), \ + INIT_ID(CORO_RUNNING), \ + INIT_ID(CORO_SUSPENDED), \ INIT_ID(Emax), \ INIT_ID(Emin), \ INIT_ID(FINISHED), \ INIT_ID(False), \ + INIT_ID(GEN_CLOSED), \ + INIT_ID(GEN_CREATED), \ + INIT_ID(GEN_RUNNING), \ + INIT_ID(GEN_SUSPENDED), \ INIT_ID(JSONDecodeError), \ INIT_ID(PENDING), \ INIT_ID(Py_Repr), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 44063794293990..10085149f09b6c 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -12,10 +12,42 @@ extern "C" { static inline void _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { PyObject *string; + string = &_Py_ID(AGEN_CLOSED); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(AGEN_CREATED); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(AGEN_RUNNING); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(AGEN_SUSPENDED); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(CANCELLED); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(CORO_CLOSED); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(CORO_CREATED); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(CORO_RUNNING); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(CORO_SUSPENDED); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(Emax); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -32,6 +64,22 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(GEN_CLOSED); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(GEN_CREATED); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(GEN_RUNNING); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(GEN_SUSPENDED); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(JSONDecodeError); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Lib/inspect.py b/Lib/inspect.py index 0dba3c6628c6e5..0eed68d17c702b 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1813,13 +1813,7 @@ def getgeneratorstate(generator): GEN_SUSPENDED: Currently suspended at a yield expression. GEN_CLOSED: Execution has completed. """ - if generator.gi_running: - return GEN_RUNNING - if generator.gi_suspended: - return GEN_SUSPENDED - if generator.gi_frame is None: - return GEN_CLOSED - return GEN_CREATED + return generator.gi_state def getgeneratorlocals(generator): @@ -1855,13 +1849,7 @@ def getcoroutinestate(coroutine): CORO_SUSPENDED: Currently suspended at an await expression. CORO_CLOSED: Execution has completed. """ - if coroutine.cr_running: - return CORO_RUNNING - if coroutine.cr_suspended: - return CORO_SUSPENDED - if coroutine.cr_frame is None: - return CORO_CLOSED - return CORO_CREATED + return coroutine.cr_state def getcoroutinelocals(coroutine): @@ -1894,13 +1882,7 @@ def getasyncgenstate(agen): AGEN_SUSPENDED: Currently suspended at a yield expression. AGEN_CLOSED: Execution has completed. """ - if agen.ag_running: - return AGEN_RUNNING - if agen.ag_suspended: - return AGEN_SUSPENDED - if agen.ag_frame is None: - return AGEN_CLOSED - return AGEN_CREATED + return agen.ag_state def getasyncgenlocals(agen): diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index c181da2e349915..6eb25960101a9e 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -1366,7 +1366,7 @@ def b(): >>> type(i) >>> [s for s in dir(i) if not s.startswith('_')] -['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_suspended', 'gi_yieldfrom', 'send', 'throw'] +['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_state', 'gi_suspended', 'gi_yieldfrom', 'send', 'throw'] >>> from test.support import HAVE_DOCSTRINGS >>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).') Implement next(self). diff --git a/Lib/types.py b/Lib/types.py index 73a69c40c8d4b8..99f23c3f44270f 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -279,6 +279,12 @@ def gi_yieldfrom(self): @property def gi_suspended(self): return self.__wrapped.gi_suspended + @property + def gi_state(self): + return self.__wrapped.gi_state + @property + def cr_state(self): + return self.__wrapped.gi_state.replace('GEN_', 'CORO_') cr_code = gi_code cr_frame = gi_frame cr_running = gi_running diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-02-17-50-14.gh-issue-120321.Xfr7tL.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-02-17-50-14.gh-issue-120321.Xfr7tL.rst new file mode 100644 index 00000000000000..fdc019a7d67e57 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-02-17-50-14.gh-issue-120321.Xfr7tL.rst @@ -0,0 +1,6 @@ +Add :attr:`~generator.gi_state`, :attr:`~coroutine.cr_state`, and +:attr:`~types.AsyncGeneratorType.ag_state` attributes that return the current +state of generators, coroutines, and async generators as a string (e.g., +``GEN_RUNNING``). The :mod:`inspect` module functions +:func:`~inspect.getgeneratorstate`, :func:`~inspect.getcoroutinestate`, and +:func:`~inspect.getasyncgenstate` now return these attributes directly. diff --git a/Objects/genobject.c b/Objects/genobject.c index 5ff4618255c852..50780fe9ab4205 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -926,6 +926,26 @@ gen_getsuspended(PyObject *self, void *Py_UNUSED(ignored)) return FRAME_STATE_SUSPENDED(frame_state) ? Py_True : Py_False; } +static PyObject * +gen_getstate(PyObject *self, void *Py_UNUSED(ignored)) +{ + PyGenObject *gen = _PyGen_CAST(self); + int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state); + + static PyObject *const state_strings[] = { + [FRAME_CREATED] = &_Py_ID(GEN_CREATED), + [FRAME_SUSPENDED] = &_Py_ID(GEN_SUSPENDED), + [FRAME_SUSPENDED_YIELD_FROM] = &_Py_ID(GEN_SUSPENDED), + [FRAME_SUSPENDED_YIELD_FROM_LOCKED] = &_Py_ID(GEN_SUSPENDED), + [FRAME_EXECUTING] = &_Py_ID(GEN_RUNNING), + [FRAME_CLEARED] = &_Py_ID(GEN_CLOSED), + }; + + assert(frame_state >= 0 && + (size_t)frame_state < Py_ARRAY_LENGTH(state_strings)); + return Py_NewRef(state_strings[frame_state]); +} + static PyObject * _gen_getframe(PyGenObject *gen, const char *const name) { @@ -974,6 +994,8 @@ static PyGetSetDef gen_getsetlist[] = { {"gi_frame", gen_getframe, NULL, NULL}, {"gi_suspended", gen_getsuspended, NULL, NULL}, {"gi_code", gen_getcode, NULL, NULL}, + {"gi_state", gen_getstate, NULL, + PyDoc_STR("state of the generator")}, {NULL} /* Sentinel */ }; @@ -1291,6 +1313,26 @@ cr_getcode(PyObject *coro, void *Py_UNUSED(ignored)) return _gen_getcode(_PyGen_CAST(coro), "cr_code"); } +static PyObject * +cr_getstate(PyObject *self, void *Py_UNUSED(ignored)) +{ + PyGenObject *gen = _PyGen_CAST(self); + int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state); + + static PyObject *const state_strings[] = { + [FRAME_CREATED] = &_Py_ID(CORO_CREATED), + [FRAME_SUSPENDED] = &_Py_ID(CORO_SUSPENDED), + [FRAME_SUSPENDED_YIELD_FROM] = &_Py_ID(CORO_SUSPENDED), + [FRAME_SUSPENDED_YIELD_FROM_LOCKED] = &_Py_ID(CORO_SUSPENDED), + [FRAME_EXECUTING] = &_Py_ID(CORO_RUNNING), + [FRAME_CLEARED] = &_Py_ID(CORO_CLOSED), + }; + + assert(frame_state >= 0 && + (size_t)frame_state < Py_ARRAY_LENGTH(state_strings)); + return Py_NewRef(state_strings[frame_state]); +} + static PyGetSetDef coro_getsetlist[] = { {"__name__", gen_get_name, gen_set_name, PyDoc_STR("name of the coroutine")}, @@ -1302,6 +1344,8 @@ static PyGetSetDef coro_getsetlist[] = { {"cr_frame", cr_getframe, NULL, NULL}, {"cr_code", cr_getcode, NULL, NULL}, {"cr_suspended", gen_getsuspended, NULL, NULL}, + {"cr_state", cr_getstate, NULL, + PyDoc_STR("state of the coroutine")}, {NULL} /* Sentinel */ }; @@ -1717,6 +1761,26 @@ ag_getcode(PyObject *gen, void *Py_UNUSED(ignored)) return _gen_getcode((PyGenObject*)gen, "ag_code"); } +static PyObject * +ag_getstate(PyObject *self, void *Py_UNUSED(ignored)) +{ + PyGenObject *gen = _PyGen_CAST(self); + int8_t frame_state = FT_ATOMIC_LOAD_INT8_RELAXED(gen->gi_frame_state); + + static PyObject *const state_strings[] = { + [FRAME_CREATED] = &_Py_ID(AGEN_CREATED), + [FRAME_SUSPENDED] = &_Py_ID(AGEN_SUSPENDED), + [FRAME_SUSPENDED_YIELD_FROM] = &_Py_ID(AGEN_SUSPENDED), + [FRAME_SUSPENDED_YIELD_FROM_LOCKED] = &_Py_ID(AGEN_SUSPENDED), + [FRAME_EXECUTING] = &_Py_ID(AGEN_RUNNING), + [FRAME_CLEARED] = &_Py_ID(AGEN_CLOSED), + }; + + assert(frame_state >= 0 && + (size_t)frame_state < Py_ARRAY_LENGTH(state_strings)); + return Py_NewRef(state_strings[frame_state]); +} + static PyGetSetDef async_gen_getsetlist[] = { {"__name__", gen_get_name, gen_set_name, PyDoc_STR("name of the async generator")}, @@ -1727,6 +1791,8 @@ static PyGetSetDef async_gen_getsetlist[] = { {"ag_frame", ag_getframe, NULL, NULL}, {"ag_code", ag_getcode, NULL, NULL}, {"ag_suspended", gen_getsuspended, NULL, NULL}, + {"ag_state", ag_getstate, NULL, + PyDoc_STR("state of the async generator")}, {NULL} /* Sentinel */ }; diff --git a/Programs/test_frozenmain.h b/Programs/test_frozenmain.h index dbeedb7ffe0ce6..b9bf4134b59718 100644 --- a/Programs/test_frozenmain.h +++ b/Programs/test_frozenmain.h @@ -13,10 +13,10 @@ unsigned char M_test_frozenmain[] = { 82,5,93,6,12,0,82,6,93,5,93,6,44,26,0,0, 0,0,0,0,0,0,0,0,12,0,50,4,52,1,0,0, 0,0,0,0,31,0,75,26,0,0,9,0,30,0,82,1, - 35,0,41,8,233,0,0,0,0,78,122,18,70,114,111,122, - 101,110,32,72,101,108,108,111,32,87,111,114,108,100,122,8, + 35,0,41,8,233,0,0,0,0,78,218,18,70,114,111,122, + 101,110,32,72,101,108,108,111,32,87,111,114,108,100,218,8, 115,121,115,46,97,114,103,118,218,6,99,111,110,102,105,103, - 122,7,99,111,110,102,105,103,32,122,2,58,32,41,5,218, + 218,7,99,111,110,102,105,103,32,218,2,58,32,41,5,218, 12,112,114,111,103,114,97,109,95,110,97,109,101,218,10,101, 120,101,99,117,116,97,98,108,101,218,15,117,115,101,95,101, 110,118,105,114,111,110,109,101,110,116,218,17,99,111,110,102, @@ -25,15 +25,15 @@ unsigned char M_test_frozenmain[] = { 3,115,121,115,218,17,95,116,101,115,116,105,110,116,101,114, 110,97,108,99,97,112,105,218,5,112,114,105,110,116,218,4, 97,114,103,118,218,11,103,101,116,95,99,111,110,102,105,103, - 115,114,3,0,0,0,218,3,107,101,121,169,0,243,0,0, + 115,114,5,0,0,0,218,3,107,101,121,169,0,243,0,0, 0,0,218,18,116,101,115,116,95,102,114,111,122,101,110,109, 97,105,110,46,112,121,218,8,60,109,111,100,117,108,101,62, - 114,18,0,0,0,1,0,0,0,115,94,0,0,0,240,3, + 114,22,0,0,0,1,0,0,0,115,94,0,0,0,240,3, 1,1,1,243,8,0,1,11,219,0,24,225,0,5,208,6, 26,212,0,27,217,0,5,128,106,144,35,151,40,145,40,212, 0,27,216,9,26,215,9,38,210,9,38,211,9,40,168,24, 213,9,50,128,6,243,2,6,12,2,128,67,241,14,0,5, 10,136,71,144,67,144,53,152,2,152,54,160,35,157,59,152, - 45,208,10,40,214,4,41,243,15,6,12,2,114,16,0,0, + 45,208,10,40,214,4,41,243,15,6,12,2,114,20,0,0, 0, }; From 86c06fa3308395d88cc33579666589354489a5d4 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 3 Feb 2026 10:18:25 -0500 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Kumar Aditya --- Objects/genobject.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Objects/genobject.c b/Objects/genobject.c index 50780fe9ab4205..5088500fc4142b 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -943,7 +943,7 @@ gen_getstate(PyObject *self, void *Py_UNUSED(ignored)) assert(frame_state >= 0 && (size_t)frame_state < Py_ARRAY_LENGTH(state_strings)); - return Py_NewRef(state_strings[frame_state]); + return state_strings[frame_state]; } static PyObject * @@ -1330,7 +1330,7 @@ cr_getstate(PyObject *self, void *Py_UNUSED(ignored)) assert(frame_state >= 0 && (size_t)frame_state < Py_ARRAY_LENGTH(state_strings)); - return Py_NewRef(state_strings[frame_state]); + return state_strings[frame_state]; } static PyGetSetDef coro_getsetlist[] = { @@ -1778,7 +1778,7 @@ ag_getstate(PyObject *self, void *Py_UNUSED(ignored)) assert(frame_state >= 0 && (size_t)frame_state < Py_ARRAY_LENGTH(state_strings)); - return Py_NewRef(state_strings[frame_state]); + return state_strings[frame_state]; } static PyGetSetDef async_gen_getsetlist[] = { From 71a9532d43859f390ab3052c414fe147331cc228 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 3 Feb 2026 10:20:49 -0500 Subject: [PATCH 3/6] Add versionchanged markup --- Doc/library/inspect.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 3f19ad5d18842b..f6bc904bdab4bd 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -360,6 +360,11 @@ attributes (see :ref:`import-mod-attrs` for module attributes): Add ``f_generator`` attribute to frames. +.. versionchanged:: next + + Add ``gi_state`` attribute to generators, ``cr_state`` attribute to + coroutines, and ``ag_state`` attribute to async generators. + .. function:: getmembers(object[, predicate]) Return all the members of an object in a list of ``(name, value)`` From be2572035b9004587a06ca8b7a41a96926897a9d Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 3 Feb 2026 10:27:14 -0500 Subject: [PATCH 4/6] Fix NEWS markup --- .../2026-02-02-17-50-14.gh-issue-120321.Xfr7tL.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-02-17-50-14.gh-issue-120321.Xfr7tL.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-02-17-50-14.gh-issue-120321.Xfr7tL.rst index fdc019a7d67e57..3e868c837839e2 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-02-17-50-14.gh-issue-120321.Xfr7tL.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-02-17-50-14.gh-issue-120321.Xfr7tL.rst @@ -1,6 +1,5 @@ -Add :attr:`~generator.gi_state`, :attr:`~coroutine.cr_state`, and -:attr:`~types.AsyncGeneratorType.ag_state` attributes that return the current -state of generators, coroutines, and async generators as a string (e.g., -``GEN_RUNNING``). The :mod:`inspect` module functions +Add ``gi_state``, ``cr_state``, and ``ag_state`` attributes to generators, +coroutines, and async generators that return the current state as a string +(e.g., ``GEN_RUNNING``). The :mod:`inspect` module functions :func:`~inspect.getgeneratorstate`, :func:`~inspect.getcoroutinestate`, and :func:`~inspect.getasyncgenstate` now return these attributes directly. From 59a219b5b623cc0f3f9757ca97251c46ee9d3f7b Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 3 Feb 2026 10:59:02 -0500 Subject: [PATCH 5/6] Regen test_frozenmain.h --- Programs/test_frozenmain.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Programs/test_frozenmain.h b/Programs/test_frozenmain.h index b9bf4134b59718..dbeedb7ffe0ce6 100644 --- a/Programs/test_frozenmain.h +++ b/Programs/test_frozenmain.h @@ -13,10 +13,10 @@ unsigned char M_test_frozenmain[] = { 82,5,93,6,12,0,82,6,93,5,93,6,44,26,0,0, 0,0,0,0,0,0,0,0,12,0,50,4,52,1,0,0, 0,0,0,0,31,0,75,26,0,0,9,0,30,0,82,1, - 35,0,41,8,233,0,0,0,0,78,218,18,70,114,111,122, - 101,110,32,72,101,108,108,111,32,87,111,114,108,100,218,8, + 35,0,41,8,233,0,0,0,0,78,122,18,70,114,111,122, + 101,110,32,72,101,108,108,111,32,87,111,114,108,100,122,8, 115,121,115,46,97,114,103,118,218,6,99,111,110,102,105,103, - 218,7,99,111,110,102,105,103,32,218,2,58,32,41,5,218, + 122,7,99,111,110,102,105,103,32,122,2,58,32,41,5,218, 12,112,114,111,103,114,97,109,95,110,97,109,101,218,10,101, 120,101,99,117,116,97,98,108,101,218,15,117,115,101,95,101, 110,118,105,114,111,110,109,101,110,116,218,17,99,111,110,102, @@ -25,15 +25,15 @@ unsigned char M_test_frozenmain[] = { 3,115,121,115,218,17,95,116,101,115,116,105,110,116,101,114, 110,97,108,99,97,112,105,218,5,112,114,105,110,116,218,4, 97,114,103,118,218,11,103,101,116,95,99,111,110,102,105,103, - 115,114,5,0,0,0,218,3,107,101,121,169,0,243,0,0, + 115,114,3,0,0,0,218,3,107,101,121,169,0,243,0,0, 0,0,218,18,116,101,115,116,95,102,114,111,122,101,110,109, 97,105,110,46,112,121,218,8,60,109,111,100,117,108,101,62, - 114,22,0,0,0,1,0,0,0,115,94,0,0,0,240,3, + 114,18,0,0,0,1,0,0,0,115,94,0,0,0,240,3, 1,1,1,243,8,0,1,11,219,0,24,225,0,5,208,6, 26,212,0,27,217,0,5,128,106,144,35,151,40,145,40,212, 0,27,216,9,26,215,9,38,210,9,38,211,9,40,168,24, 213,9,50,128,6,243,2,6,12,2,128,67,241,14,0,5, 10,136,71,144,67,144,53,152,2,152,54,160,35,157,59,152, - 45,208,10,40,214,4,41,243,15,6,12,2,114,20,0,0, + 45,208,10,40,214,4,41,243,15,6,12,2,114,16,0,0, 0, }; From b7fb6952706177eec80564ebc5eaa5719ea30bf4 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 3 Feb 2026 11:53:44 -0500 Subject: [PATCH 6/6] Add state_strings arrays to c-globals ignore list --- Tools/c-analyzer/cpython/ignored.tsv | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index 6ad3fc5f76e57a..91bbf94990ecc1 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -332,6 +332,9 @@ Objects/complexobject.c - c_1 - Objects/exceptions.c - static_exceptions - Objects/genobject.c - ASYNC_GEN_IGNORED_EXIT_MSG - Objects/genobject.c - NON_INIT_CORO_MSG - +Objects/genobject.c gen_getstate state_strings - +Objects/genobject.c cr_getstate state_strings - +Objects/genobject.c ag_getstate state_strings - Objects/longobject.c - _PyLong_DigitValue - Objects/longobject.c - PyLong_LAYOUT - Objects/object.c - _Py_SwappedOp -