Issue #24450: Add gi_yieldfrom to generators; cr_await to coroutines.

Patch by Benno Leslie and Yury Selivanov.
This commit is contained in:
Yury Selivanov 2015-07-03 00:23:30 -04:00
parent b32b998bf7
commit e13f8f3cab
6 changed files with 101 additions and 1 deletions

View File

@ -182,12 +182,19 @@ attributes:
+-----------+-----------------+---------------------------+
| | __qualname__ | qualified name |
+-----------+-----------------+---------------------------+
| | cr_await | object being awaited on, |
| | | or ``None`` |
+-----------+-----------------+---------------------------+
| | cr_frame | frame |
+-----------+-----------------+---------------------------+
| | cr_running | is the coroutine running? |
+-----------+-----------------+---------------------------+
| | cr_code | code |
+-----------+-----------------+---------------------------+
| | gi_yieldfrom | object being iterated by |
| | | ``yield from``, or |
| | | ``None`` |
+-----------+-----------------+---------------------------+
| builtin | __doc__ | documentation string |
+-----------+-----------------+---------------------------+
| | __name__ | original name of this |

View File

@ -84,6 +84,9 @@ New built-in features:
* ``b'\xf0\x9f\x90\x8d'.hex()``, ``bytearray(b'\xf0\x9f\x90\x8d').hex()``,
``memoryview(b'\xf0\x9f\x90\x8d').hex()``: :issue:`9951` - A ``hex`` method
has been added to bytes, bytearray, and memoryview.
* Generators have new ``gi_yieldfrom`` attribute, which returns the
object being iterated by ``yield from`` expressions. (Contributed
by Benno Leslie and Yury Selivanov in :issue:`24450`.)
Implementation improvements:

View File

@ -350,6 +350,36 @@ class CoroutineTest(unittest.TestCase):
"coroutine ignored GeneratorExit"):
c.close()
def test_cr_await(self):
@types.coroutine
def a():
self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_RUNNING)
self.assertIsNone(coro_b.cr_await)
yield
self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_RUNNING)
self.assertIsNone(coro_b.cr_await)
async def c():
await a()
async def b():
self.assertIsNone(coro_b.cr_await)
await c()
self.assertIsNone(coro_b.cr_await)
coro_b = b()
self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_CREATED)
self.assertIsNone(coro_b.cr_await)
coro_b.send(None)
self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_SUSPENDED)
self.assertEqual(coro_b.cr_await.cr_await.gi_code.co_name, 'a')
with self.assertRaises(StopIteration):
coro_b.send(None) # complete coroutine
self.assertEqual(inspect.getcoroutinestate(coro_b), inspect.CORO_CLOSED)
self.assertIsNone(coro_b.cr_await)
def test_corotype_1(self):
ct = types.CoroutineType
self.assertIn('into coroutine', ct.send.__doc__)

View File

@ -3,6 +3,8 @@ import sys
import unittest
import warnings
import weakref
import inspect
import types
from test import support
@ -259,6 +261,39 @@ class ExceptionTest(unittest.TestCase):
next(g)
class YieldFromTests(unittest.TestCase):
def test_generator_gi_yieldfrom(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)
tutorial_tests = """
Let's try a simple generator:
@ -624,7 +659,7 @@ From the Iterators list, about the types of these things.
>>> type(i)
<class 'generator'>
>>> [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).

View File

@ -27,6 +27,9 @@ Core and Builtins
used in types.coroutine to be instance of collections.abc.Generator;
inspect.isawaitable was removed (use collections.abc.Awaitable).
- Issue #24450: Add gi_yieldfrom to generators and cr_await to coroutines.
Contributed by Benno Leslie and Yury Selivanov.
Library
-------

View File

@ -552,11 +552,22 @@ 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 */
};
@ -776,11 +787,22 @@ coro_await(PyCoroObject *coro)
return (PyObject *)cw;
}
static PyObject *
coro_get_cr_await(PyCoroObject *coro)
{
PyObject *yf = gen_yf((PyGenObject *) coro);
if (yf == NULL)
Py_RETURN_NONE;
return yf;
}
static PyGetSetDef coro_getsetlist[] = {
{"__name__", (getter)gen_get_name, (setter)gen_set_name,
PyDoc_STR("name of the coroutine")},
{"__qualname__", (getter)gen_get_qualname, (setter)gen_set_qualname,
PyDoc_STR("qualified name of the coroutine")},
{"cr_await", (getter)coro_get_cr_await, NULL,
PyDoc_STR("object being awaited on, or None")},
{NULL} /* Sentinel */
};