Commit cb53a50c authored by Stefan Behnel's avatar Stefan Behnel

Implement "gen.gi_frame" and "coro.cr_frame" attributes on generators and...

Implement "gen.gi_frame" and "coro.cr_frame" attributes on generators and coroutines that return an inspectable (although otherwise dead) frame object.

Closes https://github.com/cython/cython/issues/2306
parent 2dd3be0a
......@@ -17,6 +17,11 @@ Bugs fixed
Also, related C compiler warnings about deprecated C-API usage were resolved.
(Github issue #3925)
* The attributes ``gen.gi_frame`` and ``coro.cr_frame`` of Cython compiled
generators and coroutines now return an actual frame object for introspection,
instead of ``None``.
(Github issue #2306)
0.29.23 (2021-04-14)
====================
......
......@@ -388,6 +388,7 @@ typedef struct {
PyObject *gi_qualname;
PyObject *gi_modulename;
PyObject *gi_code;
PyObject *gi_frame;
int resume_label;
// using T_BOOL for property below requires char value
char is_running;
......@@ -1137,6 +1138,7 @@ static int __Pyx_Coroutine_clear(PyObject *self) {
}
#endif
Py_CLEAR(gen->gi_code);
Py_CLEAR(gen->gi_frame);
Py_CLEAR(gen->gi_name);
Py_CLEAR(gen->gi_qualname);
Py_CLEAR(gen->gi_modulename);
......@@ -1369,6 +1371,31 @@ __Pyx_Coroutine_set_qualname(__pyx_CoroutineObject *self, PyObject *value, CYTHO
return 0;
}
static PyObject *
__Pyx_Coroutine_get_frame(__pyx_CoroutineObject *self, CYTHON_UNUSED void *context)
{
PyObject *frame = self->gi_frame;
if (!frame) {
if (unlikely(!self->gi_code)) {
// Avoid doing something stupid, e.g. during garbage collection.
Py_RETURN_NONE;
}
frame = (PyObject *) PyFrame_New(
PyThreadState_Get(), /*PyThreadState *tstate,*/
(PyCodeObject*) self->gi_code, /*PyCodeObject *code,*/
$moddict_cname, /*PyObject *globals,*/
0 /*PyObject *locals*/
);
if (unlikely(!frame))
return NULL;
// keep the frame cached once it's created
self->gi_frame = frame;
}
Py_INCREF(frame);
return frame;
}
static __pyx_CoroutineObject *__Pyx__Coroutine_New(
PyTypeObject* type, __pyx_coroutine_body_t body, PyObject *code, PyObject *closure,
PyObject *name, PyObject *qualname, PyObject *module_name) {
......@@ -1403,6 +1430,7 @@ static __pyx_CoroutineObject *__Pyx__Coroutine_NewInit(
gen->gi_modulename = module_name;
Py_XINCREF(code);
gen->gi_code = code;
gen->gi_frame = NULL;
PyObject_GC_Track(gen);
return gen;
......@@ -1558,13 +1586,6 @@ static PyObject *__Pyx_Coroutine_await(PyObject *coroutine) {
}
#endif
static PyObject *
__Pyx_Coroutine_get_frame(CYTHON_UNUSED __pyx_CoroutineObject *self, CYTHON_UNUSED void *context)
{
// Fake implementation that always returns None, but at least does not raise an AttributeError.
Py_RETURN_NONE;
}
#if CYTHON_COMPILING_IN_CPYTHON && PY_MAJOR_VERSION >= 3 && PY_VERSION_HEX < 0x030500B1
static PyObject *__Pyx_Coroutine_compare(PyObject *obj, PyObject *other, int op) {
PyObject* result;
......@@ -1844,6 +1865,8 @@ static PyGetSetDef __pyx_Generator_getsets[] = {
(char*) PyDoc_STR("name of the generator"), 0},
{(char *) "__qualname__", (getter)__Pyx_Coroutine_get_qualname, (setter)__Pyx_Coroutine_set_qualname,
(char*) PyDoc_STR("qualified name of the generator"), 0},
{(char *) "gi_frame", (getter)__Pyx_Coroutine_get_frame, NULL,
(char*) PyDoc_STR("Frame of the generator"), 0},
{0, 0, 0, 0, 0}
};
......
# cython: language_level=3
# mode: run
# tag: pep492, pure3.5
async def test_coroutine_frame(awaitable):
"""
>>> class Awaitable(object):
... def __await__(self):
... return iter([2])
>>> coro = test_coroutine_frame(Awaitable())
>>> import types
>>> isinstance(coro.cr_frame, types.FrameType) or coro.cr_frame
True
>>> coro.cr_frame is coro.cr_frame # assert that it's cached
True
>>> coro.cr_frame.f_code is not None
True
>>> code_obj = coro.cr_frame.f_code
>>> code_obj.co_argcount
1
>>> code_obj.co_varnames
('awaitable', 'b')
>>> next(coro.__await__()) # avoid "not awaited" warning
2
"""
b = await awaitable
return b
......@@ -541,3 +541,23 @@ def test_generator_kwds3(**kwargs):
a
"""
yield from kwargs.keys()
def test_generator_frame(a=1):
"""
>>> gen = test_generator_frame()
>>> import types
>>> isinstance(gen.gi_frame, types.FrameType) or gen.gi_frame
True
>>> gen.gi_frame is gen.gi_frame # assert that it's cached
True
>>> gen.gi_frame.f_code is not None
True
>>> code_obj = gen.gi_frame.f_code
>>> code_obj.co_argcount
1
>>> code_obj.co_varnames
('a', 'b')
"""
b = a + 1
yield b
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment