diff -r 38f75ebfe4cd Include/genobject.h
--- a/Include/genobject.h Tue Jun 09 18:42:36 2015 +0300
+++ b/Include/genobject.h Tue Jun 09 18:05:37 2015 -0400
@@ -38,12 +38,6 @@
#define PyGen_Check(op) PyObject_TypeCheck(op, &PyGen_Type)
#define PyGen_CheckExact(op) (Py_TYPE(op) == &PyGen_Type)
-#define PyGen_CheckCoroutineExact(op) (PyGen_CheckExact(op) && \
- (((PyCodeObject*) \
- ((PyGenObject*)op)->gi_code) \
- ->co_flags & (CO_ITERABLE_COROUTINE | \
- CO_COROUTINE)))
-
PyAPI_FUNC(PyObject *) PyGen_New(struct _frame *);
PyAPI_FUNC(PyObject *) PyGen_NewWithQualName(struct _frame *,
PyObject *name, PyObject *qualname);
@@ -52,7 +46,13 @@
PyObject *_PyGen_Send(PyGenObject *, PyObject *);
PyAPI_FUNC(void) _PyGen_Finalize(PyObject *self);
-PyObject *_PyGen_GetAwaitableIter(PyObject *o);
+#ifndef Py_LIMITED_API
+PyAPI_DATA(PyTypeObject) PyCoro_Type;
+#define PyCoro_CheckExact(op) (Py_TYPE(op) == &PyCoro_Type)
+PyAPI_FUNC(PyObject *) _PyCoro_GetAwaitableIter(PyObject *o);
+PyAPI_FUNC(PyObject *) PyCoro_New(struct _frame *,
+ PyObject *name, PyObject *qualname);
+#endif
#ifdef __cplusplus
}
diff -r 38f75ebfe4cd Include/opcode.h
--- a/Include/opcode.h Tue Jun 09 18:42:36 2015 +0300
+++ b/Include/opcode.h Tue Jun 09 18:05:37 2015 -0400
@@ -45,6 +45,7 @@
#define BINARY_OR 66
#define INPLACE_POWER 67
#define GET_ITER 68
+#define GET_YIELD_FROM_ITER 69
#define PRINT_EXPR 70
#define LOAD_BUILD_CLASS 71
#define YIELD_FROM 72
diff -r 38f75ebfe4cd Lib/_collections_abc.py
--- a/Lib/_collections_abc.py Tue Jun 09 18:42:36 2015 +0300
+++ b/Lib/_collections_abc.py Tue Jun 09 18:05:37 2015 -0400
@@ -53,6 +53,11 @@
mappingproxy = type(type.__dict__)
generator = type((lambda: (yield))())
+async def _coro(): pass
+_coro = _coro()
+coroutine = type(_coro)
+_coro.close() # Prevent ResourceWarning
+
### ONE-TRICK PONIES ###
@@ -78,17 +83,12 @@
class _AwaitableMeta(ABCMeta):
def __instancecheck__(cls, instance):
- # 0x80 = CO_COROUTINE
# 0x100 = CO_ITERABLE_COROUTINE
# We don't want to import 'inspect' module, as
# a dependency for 'collections.abc'.
- CO_COROUTINES = 0x80 | 0x100
-
if (isinstance(instance, generator) and
- instance.gi_code.co_flags & CO_COROUTINES):
-
+ instance.gi_code.co_flags & 0x100):
return True
-
return super().__instancecheck__(instance)
@@ -111,6 +111,9 @@
return NotImplemented
+Awaitable.register(coroutine)
+
+
class Coroutine(Awaitable):
__slots__ = ()
@@ -159,6 +162,9 @@
return NotImplemented
+Coroutine.register(coroutine)
+
+
class AsyncIterable(metaclass=ABCMeta):
__slots__ = ()
diff -r 38f75ebfe4cd Lib/opcode.py
--- a/Lib/opcode.py Tue Jun 09 18:42:36 2015 +0300
+++ b/Lib/opcode.py Tue Jun 09 18:05:37 2015 -0400
@@ -103,6 +103,7 @@
def_op('BINARY_OR', 66)
def_op('INPLACE_POWER', 67)
def_op('GET_ITER', 68)
+def_op('GET_YIELD_FROM_ITER', 69)
def_op('PRINT_EXPR', 70)
def_op('LOAD_BUILD_CLASS', 71)
diff -r 38f75ebfe4cd Lib/test/test_coroutines.py
--- a/Lib/test/test_coroutines.py Tue Jun 09 18:42:36 2015 +0300
+++ b/Lib/test/test_coroutines.py Tue Jun 09 18:05:37 2015 -0400
@@ -24,7 +24,7 @@
def run_async(coro):
- assert coro.__class__ is types.GeneratorType
+ assert coro.__class__ in {types.GeneratorType, types.CoroutineType}
buffer = []
result = None
@@ -37,6 +37,25 @@
return buffer, result
+def run_async__await__(coro):
+ assert coro.__class__ is types.CoroutineType
+ aw = coro.__await__()
+ buffer = []
+ result = None
+ i = 0
+ while True:
+ try:
+ if i % 2:
+ buffer.append(next(aw))
+ else:
+ buffer.append(aw.send(None))
+ i += 1
+ except StopIteration as ex:
+ result = ex.args[0] if ex.args else None
+ break
+ return buffer, result
+
+
@contextlib.contextmanager
def silence_coro_gc():
with warnings.catch_warnings():
@@ -121,13 +140,15 @@
return 10
f = foo()
- self.assertIsInstance(f, types.GeneratorType)
+ self.assertIsInstance(f, types.CoroutineType)
self.assertTrue(bool(foo.__code__.co_flags & 0x80))
self.assertTrue(bool(foo.__code__.co_flags & 0x20))
self.assertTrue(bool(f.gi_code.co_flags & 0x80))
self.assertTrue(bool(f.gi_code.co_flags & 0x20))
self.assertEqual(run_async(f), ([], 10))
+ self.assertEqual(run_async__await__(foo()), ([], 10))
+
def bar(): pass
self.assertFalse(bool(bar.__code__.co_flags & 0x80))
@@ -152,7 +173,7 @@
raise StopIteration
check = lambda: self.assertRaisesRegex(
- TypeError, "coroutine-objects do not support iteration")
+ TypeError, "'coroutine' object is not iterable")
with check():
list(foo())
@@ -166,9 +187,6 @@
with check():
iter(foo())
- with check():
- next(foo())
-
with silence_coro_gc(), check():
for i in foo():
pass
@@ -185,7 +203,7 @@
await bar()
check = lambda: self.assertRaisesRegex(
- TypeError, "coroutine-objects do not support iteration")
+ TypeError, "'coroutine' object is not iterable")
with check():
for el in foo(): pass
@@ -244,6 +262,41 @@
foo()
support.gc_collect()
+ def test_func_10(self):
+ N = 0
+
+ @types.coroutine
+ def gen():
+ nonlocal N
+ try:
+ a = yield
+ yield (a ** 2)
+ except ZeroDivisionError:
+ N += 100
+ raise
+ finally:
+ N += 1
+
+ async def foo():
+ await gen()
+
+ coro = foo()
+ aw = coro.__await__()
+ self.assertIs(aw, iter(aw))
+ next(aw)
+ self.assertEqual(aw.send(10), 100)
+
+ self.assertEqual(N, 0)
+ aw.close()
+ self.assertEqual(N, 1)
+
+ coro = foo()
+ aw = coro.__await__()
+ next(aw)
+ with self.assertRaises(ZeroDivisionError):
+ aw.throw(ZeroDivisionError, None, None)
+ self.assertEqual(N, 102)
+
def test_await_1(self):
async def foo():
@@ -262,6 +315,7 @@
await AsyncYieldFrom([1, 2, 3])
self.assertEqual(run_async(foo()), ([1, 2, 3], None))
+ self.assertEqual(run_async__await__(foo()), ([1, 2, 3], None))
def test_await_4(self):
async def bar():
diff -r 38f75ebfe4cd Lib/test/test_types.py
--- a/Lib/test/test_types.py Tue Jun 09 18:42:36 2015 +0300
+++ b/Lib/test/test_types.py Tue Jun 09 18:05:37 2015 -0400
@@ -1210,6 +1210,21 @@
'callable wrapped .* non-coroutine'):
foo()
+ def test_async_def(self):
+ # Test that types.coroutine passes 'async def' coroutines
+ # without modification
+
+ async def foo(): pass
+ foo_flags = foo.__code__.co_flags
+ self.assertIs(foo, types.coroutine(foo))
+ self.assertEqual(foo.__code__.co_flags, foo_flags)
+
+ @types.coroutine
+ def bar(): return foo()
+ coro = bar()
+ self.assertEqual(coro.gi_code.co_flags, foo_flags)
+ coro.close()
+
def test_duck_coro(self):
class CoroLike:
def send(self): pass
diff -r 38f75ebfe4cd Lib/types.py
--- a/Lib/types.py Tue Jun 09 18:42:36 2015 +0300
+++ b/Lib/types.py Tue Jun 09 18:05:37 2015 -0400
@@ -19,6 +19,11 @@
yield 1
GeneratorType = type(_g())
+async def _c(): pass
+_c = _c()
+CoroutineType = type(_c)
+_c.close() # Prevent ResourceWarning
+
class _C:
def _m(self): pass
MethodType = type(_C()._m)
@@ -40,7 +45,7 @@
GetSetDescriptorType = type(FunctionType.__code__)
MemberDescriptorType = type(FunctionType.__globals__)
-del sys, _f, _g, _C, # Not for export
+del sys, _f, _g, _C, _c, # Not for export
# Provide a PEP 3115 compliant mechanism for class creation
@@ -167,26 +172,30 @@
# We don't want to import 'dis' or 'inspect' just for
# these constants.
CO_GENERATOR = 0x20
+ CO_COROUTINE = 0x80
CO_ITERABLE_COROUTINE = 0x100
if not callable(func):
raise TypeError('types.coroutine() expects a callable')
if (isinstance(func, FunctionType) and
- isinstance(getattr(func, '__code__', None), CodeType) and
- (func.__code__.co_flags & CO_GENERATOR)):
+ isinstance(getattr(func, '__code__', None), CodeType)):
- # TODO: Implement this in C.
- co = func.__code__
- func.__code__ = CodeType(
- co.co_argcount, co.co_kwonlyargcount, co.co_nlocals,
- co.co_stacksize,
- co.co_flags | CO_ITERABLE_COROUTINE,
- co.co_code,
- co.co_consts, co.co_names, co.co_varnames, co.co_filename,
- co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars,
- co.co_cellvars)
- return func
+ if func.__code__.co_flags & CO_COROUTINE:
+ return func
+
+ if func.__code__.co_flags & CO_GENERATOR:
+ # TODO: Implement this in C.
+ co = func.__code__
+ func.__code__ = CodeType(
+ co.co_argcount, co.co_kwonlyargcount, co.co_nlocals,
+ co.co_stacksize,
+ co.co_flags | CO_ITERABLE_COROUTINE,
+ co.co_code,
+ co.co_consts, co.co_names, co.co_varnames, co.co_filename,
+ co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars,
+ co.co_cellvars)
+ return func
# The following code is primarily to support functions that
# return generator-like objects (for instance generators
@@ -218,6 +227,8 @@
@_functools.wraps(func)
def wrapped(*args, **kwargs):
coro = func(*args, **kwargs)
+ if coro.__class__ is CoroutineType:
+ return coro
if coro.__class__ is GeneratorType:
return GeneratorWrapper(coro)
# slow checks
diff -r 38f75ebfe4cd Objects/genobject.c
--- a/Objects/genobject.c Tue Jun 09 18:42:36 2015 +0300
+++ b/Objects/genobject.c Tue Jun 09 18:05:37 2015 -0400
@@ -432,12 +432,6 @@
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);
}
@@ -494,14 +488,8 @@
static PyObject *
gen_repr(PyGenObject *gen)
{
- if (PyGen_CheckCoroutineExact(gen)) {
- return PyUnicode_FromFormat("