Skip to content

Commit e13f8f3

Browse files
committed
Issue python#24450: Add gi_yieldfrom to generators; cr_await to coroutines.
Patch by Benno Leslie and Yury Selivanov.
1 parent b32b998 commit e13f8f3

File tree

6 files changed

+101
-1
lines changed

6 files changed

+101
-1
lines changed

Doc/library/inspect.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,19 @@ attributes:
182182
+-----------+-----------------+---------------------------+
183183
| | __qualname__ | qualified name |
184184
+-----------+-----------------+---------------------------+
185+
| | cr_await | object being awaited on, |
186+
| | | or ``None`` |
187+
+-----------+-----------------+---------------------------+
185188
| | cr_frame | frame |
186189
+-----------+-----------------+---------------------------+
187190
| | cr_running | is the coroutine running? |
188191
+-----------+-----------------+---------------------------+
189192
| | cr_code | code |
190193
+-----------+-----------------+---------------------------+
194+
| | gi_yieldfrom | object being iterated by |
195+
| | | ``yield from``, or |
196+
| | | ``None`` |
197+
+-----------+-----------------+---------------------------+
191198
| builtin | __doc__ | documentation string |
192199
+-----------+-----------------+---------------------------+
193200
| | __name__ | original name of this |

Doc/whatsnew/3.5.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ New built-in features:
8484
* ``b'\xf0\x9f\x90\x8d'.hex()``, ``bytearray(b'\xf0\x9f\x90\x8d').hex()``,
8585
``memoryview(b'\xf0\x9f\x90\x8d').hex()``: :issue:`9951` - A ``hex`` method
8686
has been added to bytes, bytearray, and memoryview.
87+
* Generators have new ``gi_yieldfrom`` attribute, which returns the
88+
object being iterated by ``yield from`` expressions. (Contributed
89+
by Benno Leslie and Yury Selivanov in :issue:`24450`.)
8790

8891
Implementation improvements:
8992

Lib/test/test_coroutines.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,36 @@ async def coro():
350350
"coroutine ignored GeneratorExit"):
351351
c.close()
352352

353+
def test_cr_await(self):
354+
@types.coroutine
355+
def a():
356+
self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_RUNNING)
357+
self.assertIsNone(coro_b.cr_await)
358+
yield
359+
self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_RUNNING)
360+
self.assertIsNone(coro_b.cr_await)
361+
362+
async def c():
363+
await a()
364+
365+
async def b():
366+
self.assertIsNone(coro_b.cr_await)
367+
await c()
368+
self.assertIsNone(coro_b.cr_await)
369+
370+
coro_b = b()
371+
self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_CREATED)
372+
self.assertIsNone(coro_b.cr_await)
373+
374+
coro_b.send(None)
375+
self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_SUSPENDED)
376+
self.assertEqual(coro_b.cr_await.cr_await.gi_code.co_name, 'a')
377+
378+
with self.assertRaises(StopIteration):
379+
coro_b.send(None) # complete coroutine
380+
self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_CLOSED)
381+
self.assertIsNone(coro_b.cr_await)
382+
353383
def test_corotype_1(self):
354384
ct = types.CoroutineType
355385
self.assertIn('into coroutine', ct.send.__doc__)

Lib/test/test_generators.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import unittest
44
import warnings
55
import weakref
6+
import inspect
7+
import types
68

79
from test import support
810

@@ -259,6 +261,39 @@ def f():
259261
next(g)
260262

261263

264+
class YieldFromTests(unittest.TestCase):
265+
def test_generator_gi_yieldfrom(self):
266+
def a():
267+
self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_RUNNING)
268+
self.assertIsNone(gen_b.gi_yieldfrom)
269+
yield
270+
self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_RUNNING)
271+
self.assertIsNone(gen_b.gi_yieldfrom)
272+
273+
def b():
274+
self.assertIsNone(gen_b.gi_yieldfrom)
275+
yield from a()
276+
self.assertIsNone(gen_b.gi_yieldfrom)
277+
yield
278+
self.assertIsNone(gen_b.gi_yieldfrom)
279+
280+
gen_b = b()
281+
self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_CREATED)
282+
self.assertIsNone(gen_b.gi_yieldfrom)
283+
284+
gen_b.send(None)
285+
self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_SUSPENDED)
286+
self.assertEqual(gen_b.gi_yieldfrom.gi_code.co_name, 'a')
287+
288+
gen_b.send(None)
289+
self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_SUSPENDED)
290+
self.assertIsNone(gen_b.gi_yieldfrom)
291+
292+
[] = gen_b # Exhaust generator
293+
self.assertEqual(inspect.getgeneratorstate(gen_b), inspect.GEN_CLOSED)
294+
self.assertIsNone(gen_b.gi_yieldfrom)
295+
296+
262297
tutorial_tests = """
263298
Let's try a simple generator:
264299
@@ -624,7 +659,7 @@ def f():
624659
>>> type(i)
625660
<class 'generator'>
626661
>>> [s for s in dir(i) if not s.startswith('_')]
627-
['close', 'gi_code', 'gi_frame', 'gi_running', 'send', 'throw']
662+
['close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
628663
>>> from test.support import HAVE_DOCSTRINGS
629664
>>> print(i.__next__.__doc__ if HAVE_DOCSTRINGS else 'Implement next(self).')
630665
Implement next(self).

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ Core and Builtins
2727
used in types.coroutine to be instance of collections.abc.Generator;
2828
inspect.isawaitable was removed (use collections.abc.Awaitable).
2929

30+
- Issue #24450: Add gi_yieldfrom to generators and cr_await to coroutines.
31+
Contributed by Benno Leslie and Yury Selivanov.
32+
3033
Library
3134
-------
3235

Objects/genobject.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,11 +552,22 @@ gen_set_qualname(PyGenObject *op, PyObject *value)
552552
return 0;
553553
}
554554

555+
static PyObject *
556+
gen_getyieldfrom(PyGenObject *gen)
557+
{
558+
PyObject *yf = gen_yf(gen);
559+
if (yf == NULL)
560+
Py_RETURN_NONE;
561+
return yf;
562+
}
563+
555564
static PyGetSetDef gen_getsetlist[] = {
556565
{"__name__", (getter)gen_get_name, (setter)gen_set_name,
557566
PyDoc_STR("name of the generator")},
558567
{"__qualname__", (getter)gen_get_qualname, (setter)gen_set_qualname,
559568
PyDoc_STR("qualified name of the generator")},
569+
{"gi_yieldfrom", (getter)gen_getyieldfrom, NULL,
570+
PyDoc_STR("object being iterated by yield from, or None")},
560571
{NULL} /* Sentinel */
561572
};
562573

@@ -776,11 +787,22 @@ coro_await(PyCoroObject *coro)
776787
return (PyObject *)cw;
777788
}
778789

790+
static PyObject *
791+
coro_get_cr_await(PyCoroObject *coro)
792+
{
793+
PyObject *yf = gen_yf((PyGenObject *) coro);
794+
if (yf == NULL)
795+
Py_RETURN_NONE;
796+
return yf;
797+
}
798+
779799
static PyGetSetDef coro_getsetlist[] = {
780800
{"__name__", (getter)gen_get_name, (setter)gen_set_name,
781801
PyDoc_STR("name of the coroutine")},
782802
{"__qualname__", (getter)gen_get_qualname, (setter)gen_set_qualname,
783803
PyDoc_STR("qualified name of the coroutine")},
804+
{"cr_await", (getter)coro_get_cr_await, NULL,
805+
PyDoc_STR("object being awaited on, or None")},
784806
{NULL} /* Sentinel */
785807
};
786808

0 commit comments

Comments
 (0)