Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`` |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a .. versionchanged:: next markup at the end of the table to mention that gi_state, ag_state, cr_state are added in Python 3.15.

+-----------------+-------------------+---------------------------+
| async generator | __name__ | name |
+-----------------+-------------------+---------------------------+
| | __qualname__ | qualified name |
Expand All @@ -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 |
Expand All @@ -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 |
Expand Down Expand Up @@ -341,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)``
Expand Down
16 changes: 7 additions & 9 deletions Include/internal/pycore_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to mention FRAME_COMPLETED removal in the commit message. I'm fine with the removal, the constant was only used in one place: in FRAME_STATE_FINISHED().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've edited the PR description and will use that when squash + merging.

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
Expand Down
12 changes: 12 additions & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 3 additions & 21 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1366,7 +1366,7 @@ def b():
>>> type(i)
<class 'generator'>
>>> [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).
Expand Down
6 changes: 6 additions & 0 deletions Lib/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
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.
66 changes: 66 additions & 0 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 state_strings[frame_state];
}

static PyObject *
_gen_getframe(PyGenObject *gen, const char *const name)
{
Expand Down Expand Up @@ -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 */
};

Expand Down Expand Up @@ -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 state_strings[frame_state];
}

static PyGetSetDef coro_getsetlist[] = {
{"__name__", gen_get_name, gen_set_name,
PyDoc_STR("name of the coroutine")},
Expand All @@ -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 */
};

Expand Down Expand Up @@ -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 state_strings[frame_state];
}

static PyGetSetDef async_gen_getsetlist[] = {
{"__name__", gen_get_name, gen_set_name,
PyDoc_STR("name of the async generator")},
Expand All @@ -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 */
};

Expand Down
Loading
Loading