diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 98b7cb7..0981250 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -178,6 +178,10 @@ attributes: +-----------+-----------------+---------------------------+ | | gi_code | code | +-----------+-----------------+---------------------------+ +| | gi_yieldfrom | object being iterated by | +| | | ``yield from``, or | +| | | ``None`` | ++-----------+-----------------+---------------------------+ | builtin | __doc__ | documentation string | +-----------+-----------------+---------------------------+ | | __name__ | original name of this | diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index fe4b138..ca5e0c3 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -3,6 +3,8 @@ import sys import unittest import warnings import weakref +import inspect +import types from test import support @@ -259,6 +261,66 @@ class ExceptionTest(unittest.TestCase): next(g) +class YieldFromTests(unittest.TestCase): + def test_generator(self): + def a(): + self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_RUNNING) + self.assertIsNone(gen_b.gi_yieldfrom) + yield + self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_RUNNING) + self.assertIsNone(gen_b.gi_yieldfrom) + + def b(): + self.assertIsNone(gen_b.gi_yieldfrom) + yield from a() + self.assertIsNone(gen_b.gi_yieldfrom) + yield + self.assertIsNone(gen_b.gi_yieldfrom) + + gen_b = b() + self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_CREATED) + self.assertIsNone(gen_b.gi_yieldfrom) + + gen_b.send(None) + self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_SUSPENDED) + self.assertEqual(gen_b.gi_yieldfrom.gi_code.co_name, 'a') + + gen_b.send(None) + self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_SUSPENDED) + self.assertIsNone(gen_b.gi_yieldfrom) + + [] = gen_b # Exhaust generator + self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_CLOSED) + self.assertIsNone(gen_b.gi_yieldfrom) + + def test_coroutine(self): + @types.coroutine + def a(): + self.assertEqual(inspect.getgeneratorstate(coro_b), inspect.GEN_RUNNING) + self.assertIsNone(coro_b.gi_yieldfrom) + yield + self.assertEqual(inspect.getgeneratorstate(coro_b), inspect.GEN_RUNNING) + self.assertIsNone(coro_b.gi_yieldfrom) + + async def b(): + self.assertIsNone(coro_b.gi_yieldfrom) + await a() + self.assertIsNone(coro_b.gi_yieldfrom) + + coro_b = b() + self.assertEqual(inspect.getgeneratorstate(coro_b), inspect.GEN_CREATED) + self.assertIsNone(coro_b.gi_yieldfrom) + + coro_b.send(None) + self.assertEqual(inspect.getgeneratorstate(coro_b), inspect.GEN_SUSPENDED) + self.assertEqual(coro_b.gi_yieldfrom.gi_code.co_name, 'a') + + with self.assertRaises(StopIteration): + coro_b.send(None) # complete coroutine + self.assertEqual(inspect.getgeneratorstate(coro_b), inspect.GEN_CLOSED) + self.assertIsNone(coro_b.gi_yieldfrom) + + tutorial_tests = """ Let's try a simple generator: @@ -624,7 +686,7 @@ From the Iterators list, about the types of these things. >>> type(i) >>> [s for s in dir(i) if not s.startswith('_')] -['close', 'gi_code', 'gi_frame', 'gi_running', 'send', 'throw'] +['close', 'gi_code', 'gi_frame', 'gi_running', '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/Objects/genobject.c b/Objects/genobject.c index 5d3b66c..ecebea6 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -569,11 +569,23 @@ gen_set_qualname(PyGenObject *op, PyObject *value) return 0; } +static PyObject * +gen_getyieldfrom(PyGenObject *gen) +{ + PyObject *yf = gen_yf(gen); + if (yf == NULL) + Py_RETURN_NONE; + + return yf; +} + static PyGetSetDef gen_getsetlist[] = { {"__name__", (getter)gen_get_name, (setter)gen_set_name, PyDoc_STR("name of the generator")}, {"__qualname__", (getter)gen_get_qualname, (setter)gen_set_qualname, PyDoc_STR("qualified name of the generator")}, + {"gi_yieldfrom", (getter)gen_getyieldfrom, NULL, + PyDoc_STR("object being iterated by yield from, or None")}, {NULL} /* Sentinel */ };