PEP 0492 -- Coroutines with async and await syntax. Issue #24017.

This commit is contained in:
Yury Selivanov 2015-05-11 22:57:16 -04:00
parent 4e6bf4b3da
commit 7544508f02
72 changed files with 9261 additions and 5739 deletions

View file

@ -472,6 +472,13 @@ SimpleExtendsException(PyExc_Exception, TypeError,
"Inappropriate argument type.");
/*
* StopAsyncIteration extends Exception
*/
SimpleExtendsException(PyExc_Exception, StopAsyncIteration,
"Signal the end from iterator.__anext__().");
/*
* StopIteration extends Exception
*/
@ -2468,6 +2475,7 @@ _PyExc_Init(PyObject *bltinmod)
PRE_INIT(BaseException)
PRE_INIT(Exception)
PRE_INIT(TypeError)
PRE_INIT(StopAsyncIteration)
PRE_INIT(StopIteration)
PRE_INIT(GeneratorExit)
PRE_INIT(SystemExit)
@ -2538,6 +2546,7 @@ _PyExc_Init(PyObject *bltinmod)
POST_INIT(BaseException)
POST_INIT(Exception)
POST_INIT(TypeError)
POST_INIT(StopAsyncIteration)
POST_INIT(StopIteration)
POST_INIT(GeneratorExit)
POST_INIT(SystemExit)

View file

@ -196,6 +196,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
case SETUP_EXCEPT:
case SETUP_FINALLY:
case SETUP_WITH:
case SETUP_ASYNC_WITH:
blockstack[blockstack_top++] = addr;
in_finally[blockstack_top-1] = 0;
break;
@ -203,7 +204,8 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
case POP_BLOCK:
assert(blockstack_top > 0);
setup_op = code[blockstack[blockstack_top-1]];
if (setup_op == SETUP_FINALLY || setup_op == SETUP_WITH) {
if (setup_op == SETUP_FINALLY || setup_op == SETUP_WITH
|| setup_op == SETUP_ASYNC_WITH) {
in_finally[blockstack_top-1] = 1;
}
else {
@ -218,7 +220,8 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
* be seeing such an END_FINALLY.) */
if (blockstack_top > 0) {
setup_op = code[blockstack[blockstack_top-1]];
if (setup_op == SETUP_FINALLY || setup_op == SETUP_WITH) {
if (setup_op == SETUP_FINALLY || setup_op == SETUP_WITH
|| setup_op == SETUP_ASYNC_WITH) {
blockstack_top--;
}
}
@ -281,6 +284,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno)
case SETUP_EXCEPT:
case SETUP_FINALLY:
case SETUP_WITH:
case SETUP_ASYNC_WITH:
delta_iblock++;
break;

View file

@ -24,6 +24,19 @@ _PyGen_Finalize(PyObject *self)
PyObject *res;
PyObject *error_type, *error_value, *error_traceback;
/* If `gen` is a coroutine, and if it was never awaited on,
issue a RuntimeWarning. */
if (gen->gi_code != NULL
&& ((PyCodeObject *)gen->gi_code)->co_flags & (CO_COROUTINE
| CO_ITERABLE_COROUTINE)
&& gen->gi_frame != NULL
&& gen->gi_frame->f_lasti == -1
&& !PyErr_Occurred()
&& PyErr_WarnFormat(PyExc_RuntimeWarning, 1,
"coroutine '%.50S' was never awaited",
gen->gi_qualname))
return;
if (gen->gi_frame == NULL || gen->gi_frame->f_stacktop == NULL)
/* Generator isn't paused, so no need to close */
return;
@ -135,7 +148,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
* a leaking StopIteration into RuntimeError (with its cause
* set appropriately). */
if ((((PyCodeObject *)gen->gi_code)->co_flags &
CO_FUTURE_GENERATOR_STOP)
(CO_FUTURE_GENERATOR_STOP | CO_COROUTINE | CO_ITERABLE_COROUTINE))
&& PyErr_ExceptionMatches(PyExc_StopIteration))
{
PyObject *exc, *val, *val2, *tb;
@ -402,6 +415,12 @@ failed_throw:
static PyObject *
gen_iternext(PyGenObject *gen)
{
if (((PyCodeObject*)gen->gi_code)->co_flags & CO_COROUTINE) {
PyErr_SetString(PyExc_TypeError,
"coroutine-objects do not support iteration");
return NULL;
}
return gen_send_ex(gen, NULL, 0);
}
@ -459,8 +478,14 @@ _PyGen_FetchStopIterationValue(PyObject **pvalue) {
static PyObject *
gen_repr(PyGenObject *gen)
{
return PyUnicode_FromFormat("<generator object %S at %p>",
gen->gi_qualname, gen);
if (PyGen_CheckCoroutineExact(gen)) {
return PyUnicode_FromFormat("<coroutine object %S at %p>",
gen->gi_qualname, gen);
}
else {
return PyUnicode_FromFormat("<generator object %S at %p>",
gen->gi_qualname, gen);
}
}
static PyObject *
@ -496,6 +521,19 @@ gen_get_qualname(PyGenObject *op)
return op->gi_qualname;
}
static PyObject *
gen_get_iter(PyGenObject *gen)
{
if (((PyCodeObject*)gen->gi_code)->co_flags & CO_COROUTINE) {
PyErr_SetString(PyExc_TypeError,
"coroutine-objects do not support iteration");
return NULL;
}
Py_INCREF(gen);
return gen;
}
static int
gen_set_qualname(PyGenObject *op, PyObject *value)
{
@ -547,7 +585,7 @@ PyTypeObject PyGen_Type = {
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_as_async */
(reprfunc)gen_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
@ -565,7 +603,7 @@ PyTypeObject PyGen_Type = {
0, /* tp_clear */
0, /* tp_richcompare */
offsetof(PyGenObject, gi_weakreflist), /* tp_weaklistoffset */
PyObject_SelfIter, /* tp_iter */
(getiterfunc)gen_get_iter, /* tp_iter */
(iternextfunc)gen_iternext, /* tp_iternext */
gen_methods, /* tp_methods */
gen_memberlist, /* tp_members */
@ -642,3 +680,57 @@ PyGen_NeedsFinalizing(PyGenObject *gen)
/* No blocks except loops, it's safe to skip finalization. */
return 0;
}
/*
* This helper function returns an awaitable for `o`:
* - `o` if `o` is a coroutine-object;
* - `type(o)->tp_as_async->am_await(o)`
*
* Raises a TypeError if it's not possible to return
* an awaitable and returns NULL.
*/
PyObject *
_PyGen_GetAwaitableIter(PyObject *o)
{
getawaitablefunc getter = NULL;
PyTypeObject *ot;
if (PyGen_CheckCoroutineExact(o)) {
/* Fast path. It's a central function for 'await'. */
Py_INCREF(o);
return o;
}
ot = Py_TYPE(o);
if (ot->tp_as_async != NULL) {
getter = ot->tp_as_async->am_await;
}
if (getter != NULL) {
PyObject *res = (*getter)(o);
if (res != NULL) {
if (!PyIter_Check(res)) {
PyErr_Format(PyExc_TypeError,
"__await__() returned non-iterator "
"of type '%.100s'",
Py_TYPE(res)->tp_name);
Py_CLEAR(res);
}
else {
if (PyGen_CheckCoroutineExact(res)) {
/* __await__ must return an *iterator*, not
a coroutine or another awaitable (see PEP 492) */
PyErr_SetString(PyExc_TypeError,
"__await__() returned a coroutine");
Py_CLEAR(res);
}
}
}
return res;
}
PyErr_Format(PyExc_TypeError,
"object %.100s can't be used in 'await' expression",
ot->tp_name);
return NULL;
}

View file

@ -2506,6 +2506,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
/* Initialize essential fields */
type->tp_as_async = &et->as_async;
type->tp_as_number = &et->as_number;
type->tp_as_sequence = &et->as_sequence;
type->tp_as_mapping = &et->as_mapping;
@ -2751,6 +2752,7 @@ PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
}
/* Initialize essential fields */
type->tp_as_async = &res->as_async;
type->tp_as_number = &res->as_number;
type->tp_as_sequence = &res->as_sequence;
type->tp_as_mapping = &res->as_mapping;
@ -4566,6 +4568,7 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
#define COPYSLOT(SLOT) \
if (!type->SLOT && SLOTDEFINED(SLOT)) type->SLOT = base->SLOT
#define COPYASYNC(SLOT) COPYSLOT(tp_as_async->SLOT)
#define COPYNUM(SLOT) COPYSLOT(tp_as_number->SLOT)
#define COPYSEQ(SLOT) COPYSLOT(tp_as_sequence->SLOT)
#define COPYMAP(SLOT) COPYSLOT(tp_as_mapping->SLOT)
@ -4615,6 +4618,15 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
COPYNUM(nb_inplace_matrix_multiply);
}
if (type->tp_as_async != NULL && base->tp_as_async != NULL) {
basebase = base->tp_base;
if (basebase->tp_as_async == NULL)
basebase = NULL;
COPYASYNC(am_await);
COPYASYNC(am_aiter);
COPYASYNC(am_anext);
}
if (type->tp_as_sequence != NULL && base->tp_as_sequence != NULL) {
basebase = base->tp_base;
if (basebase->tp_as_sequence == NULL)
@ -4884,6 +4896,8 @@ PyType_Ready(PyTypeObject *type)
/* Some more special stuff */
base = type->tp_base;
if (base != NULL) {
if (type->tp_as_async == NULL)
type->tp_as_async = base->tp_as_async;
if (type->tp_as_number == NULL)
type->tp_as_number = base->tp_as_number;
if (type->tp_as_sequence == NULL)
@ -4904,16 +4918,6 @@ PyType_Ready(PyTypeObject *type)
goto error;
}
/* Warn for a type that implements tp_compare (now known as
tp_reserved) but not tp_richcompare. */
if (type->tp_reserved && !type->tp_richcompare) {
PyErr_Format(PyExc_TypeError,
"Type %.100s defines tp_reserved (formerly tp_compare) "
"but not tp_richcompare. Comparisons may not behave as intended.",
type->tp_name);
goto error;
}
/* All done -- set the ready flag */
assert(type->tp_dict != NULL);
type->tp_flags =
@ -6265,6 +6269,59 @@ slot_tp_finalize(PyObject *self)
PyErr_Restore(error_type, error_value, error_traceback);
}
static PyObject *
slot_am_await(PyObject *self)
{
PyObject *func, *res;
_Py_IDENTIFIER(__await__);
func = lookup_method(self, &PyId___await__);
if (func != NULL) {
res = PyEval_CallObject(func, NULL);
Py_DECREF(func);
return res;
}
PyErr_Format(PyExc_AttributeError,
"object %.50s does not have __await__ method",
Py_TYPE(self)->tp_name);
return NULL;
}
static PyObject *
slot_am_aiter(PyObject *self)
{
PyObject *func, *res;
_Py_IDENTIFIER(__aiter__);
func = lookup_method(self, &PyId___aiter__);
if (func != NULL) {
res = PyEval_CallObject(func, NULL);
Py_DECREF(func);
return res;
}
PyErr_Format(PyExc_AttributeError,
"object %.50s does not have __aiter__ method",
Py_TYPE(self)->tp_name);
return NULL;
}
static PyObject *
slot_am_anext(PyObject *self)
{
PyObject *func, *res;
_Py_IDENTIFIER(__anext__);
func = lookup_method(self, &PyId___anext__);
if (func != NULL) {
res = PyEval_CallObject(func, NULL);
Py_DECREF(func);
return res;
}
PyErr_Format(PyExc_AttributeError,
"object %.50s does not have __anext__ method",
Py_TYPE(self)->tp_name);
return NULL;
}
/*
Table mapping __foo__ names to tp_foo offsets and slot_tp_foo wrapper functions.
@ -6281,6 +6338,7 @@ typedef struct wrapperbase slotdef;
#undef TPSLOT
#undef FLSLOT
#undef AMSLOT
#undef ETSLOT
#undef SQSLOT
#undef MPSLOT
@ -6299,6 +6357,8 @@ typedef struct wrapperbase slotdef;
#define ETSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
{NAME, offsetof(PyHeapTypeObject, SLOT), (void *)(FUNCTION), WRAPPER, \
PyDoc_STR(DOC)}
#define AMSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
ETSLOT(NAME, as_async.SLOT, FUNCTION, WRAPPER, DOC)
#define SQSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
ETSLOT(NAME, as_sequence.SLOT, FUNCTION, WRAPPER, DOC)
#define MPSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
@ -6378,6 +6438,13 @@ static slotdef slotdefs[] = {
"Create and return new object. See help(type) for accurate signature."),
TPSLOT("__del__", tp_finalize, slot_tp_finalize, (wrapperfunc)wrap_del, ""),
AMSLOT("__await__", am_await, slot_am_await, wrap_unaryfunc,
"__await__($self, /)\n--\n\nReturn an iterator to be used in await expression."),
AMSLOT("__aiter__", am_aiter, slot_am_aiter, wrap_unaryfunc,
"__aiter__($self, /)\n--\n\nReturn an awaitable, that resolves in asynchronous iterator."),
AMSLOT("__anext__", am_anext, slot_am_anext, wrap_unaryfunc,
"__anext__($self, /)\n--\n\nReturn a value or raise StopAsyncIteration."),
BINSLOT("__add__", nb_add, slot_nb_add,
"+"),
RBINSLOT("__radd__", nb_add, slot_nb_add,
@ -6530,6 +6597,10 @@ slotptr(PyTypeObject *type, int ioffset)
ptr = (char *)type->tp_as_number;
offset -= offsetof(PyHeapTypeObject, as_number);
}
else if ((size_t)offset >= offsetof(PyHeapTypeObject, as_async)) {
ptr = (char *)type->tp_as_async;
offset -= offsetof(PyHeapTypeObject, as_async);
}
else {
ptr = (char *)type;
}

View file

@ -75,3 +75,6 @@ offsetof(PyHeapTypeObject, ht_type.tp_getset),
offsetof(PyHeapTypeObject, ht_type.tp_free),
offsetof(PyHeapTypeObject, as_number.nb_matrix_multiply),
offsetof(PyHeapTypeObject, as_number.nb_inplace_matrix_multiply),
offsetof(PyHeapTypeObject, as_async.am_await),
offsetof(PyHeapTypeObject, as_async.am_aiter),
offsetof(PyHeapTypeObject, as_async.am_anext),

View file

@ -12,6 +12,8 @@ for line in sys.stdin:
member = m.group(1)
if member.startswith("tp_"):
member = "ht_type."+member
elif member.startswith("am_"):
member = "as_async."+member
elif member.startswith("nb_"):
member = "as_number."+member
elif member.startswith("mp_"):