summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Behnel <stefan_ml@behnel.de>2019-01-18 15:33:19 +0100
committerStefan Behnel <stefan_ml@behnel.de>2019-01-18 15:33:19 +0100
commit4a1c16a4db67a1926ca55d64fd183c302546770b (patch)
tree86d777b64c0703727298bdffda5ae918dae02649
parent8bdd78ee0a667e76d05d1ab03887ee960013b9b0 (diff)
downloadcython-gh2306_coro_frame.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.rst4
-rw-r--r--Cython/Utility/Coroutine.c36
-rw-r--r--tests/run/coroutines.py30
-rw-r--r--tests/run/generators.pyx20
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