diff options
author | Stefan Behnel <stefan_ml@behnel.de> | 2019-01-18 15:33:19 +0100 |
---|---|---|
committer | Stefan Behnel <stefan_ml@behnel.de> | 2019-01-18 15:33:19 +0100 |
commit | 4a1c16a4db67a1926ca55d64fd183c302546770b (patch) | |
tree | 86d777b64c0703727298bdffda5ae918dae02649 | |
parent | 8bdd78ee0a667e76d05d1ab03887ee960013b9b0 (diff) | |
download | cython-4a1c16a4db67a1926ca55d64fd183c302546770b.tar.gz |
Implement "gen.gi_frame" and "coro.cr_frame" attributes on generators and coroutines that return an inspectable (although otherwise dead) frame object.gh2306_coro_frame
Closes #2306.
-rw-r--r-- | CHANGES.rst | 4 | ||||
-rw-r--r-- | Cython/Utility/Coroutine.c | 36 | ||||
-rw-r--r-- | tests/run/coroutines.py | 30 | ||||
-rw-r--r-- | tests/run/generators.pyx | 20 |
4 files changed, 83 insertions, 7 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 2dab0f4f6..a78c3b5f8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,6 +20,10 @@ Features added * Properties can be defined for external extension types. Patch by Matti Picus. (Github issue #2640) +* The attributes ``gen.gi_frame`` and ``coro.cr_frame`` of Cython compiled + generators and coroutines now return an actual frame object for introspection. + (Github issue #2306) + * The builtin ``abs()`` function can now be used on C numbers in nogil code. Patch by Elliott Sales de Andrade. (Github issue #2748) diff --git a/Cython/Utility/Coroutine.c b/Cython/Utility/Coroutine.c index 54e370766..3fac6f060 100644 --- a/Cython/Utility/Coroutine.c +++ b/Cython/Utility/Coroutine.c @@ -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; @@ -1110,6 +1111,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); @@ -1342,6 +1344,30 @@ __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) { @@ -1376,6 +1402,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; @@ -1525,13 +1552,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; @@ -1799,6 +1819,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 coroutine"), 0}, {0, 0, 0, 0, 0} }; diff --git a/tests/run/coroutines.py b/tests/run/coroutines.py new file mode 100644 index 000000000..d0b9ab9db --- /dev/null +++ b/tests/run/coroutines.py @@ -0,0 +1,30 @@ +# 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 diff --git a/tests/run/generators.pyx b/tests/run/generators.pyx index 8bf35767d..f407739c8 100644 --- a/tests/run/generators.pyx +++ b/tests/run/generators.pyx @@ -502,3 +502,23 @@ def test_generator_abc(): True """ yield 1 + + +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 |