diff -r af793c7580f1 Include/genobject.h
--- a/Include/genobject.h Wed Jun 17 10:09:24 2015 -0500
+++ b/Include/genobject.h Wed Jun 17 12:46:52 2015 -0400
@@ -10,27 +10,26 @@
struct _frame; /* Avoid including frameobject.h */
+/* _PyGenObject_HEAD defines the initial segment of generator
+ and coroutine objects. */
+#define _PyGenObject_HEAD \
+ PyObject_HEAD \
+ /* The gi_ prefix is intended to remind of generator-iterator. */ \
+ /* Note: gi_frame can be NULL if the generator is "finished" */ \
+ struct _frame *gi_frame; \
+ /* True if generator is being executed. */ \
+ char gi_running; \
+ /* The code object backing the generator */ \
+ PyObject *gi_code; \
+ /* List of weak reference. */ \
+ PyObject *gi_weakreflist; \
+ /* Name of the generator. */ \
+ PyObject *gi_name; \
+ /* Qualified name of the generator. */ \
+ PyObject *gi_qualname;
+
typedef struct {
- PyObject_HEAD
- /* The gi_ prefix is intended to remind of generator-iterator. */
-
- /* Note: gi_frame can be NULL if the generator is "finished" */
- struct _frame *gi_frame;
-
- /* True if generator is being executed. */
- char gi_running;
-
- /* The code object backing the generator */
- PyObject *gi_code;
-
- /* List of weak reference. */
- PyObject *gi_weakreflist;
-
- /* Name of the generator. */
- PyObject *gi_name;
-
- /* Qualified name of the generator. */
- PyObject *gi_qualname;
+ _PyGenObject_HEAD
} PyGenObject;
PyAPI_DATA(PyTypeObject) PyGen_Type;
@@ -38,12 +37,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 +45,21 @@
PyObject *_PyGen_Send(PyGenObject *, PyObject *);
PyAPI_FUNC(void) _PyGen_Finalize(PyObject *self);
-PyObject *_PyGen_GetAwaitableIter(PyObject *o);
+#ifndef Py_LIMITED_API
+typedef struct {
+ _PyGenObject_HEAD
+} PyCoroObject;
+
+PyAPI_DATA(PyTypeObject) PyCoro_Type;
+PyAPI_DATA(PyTypeObject) _PyCoroWrapper_Type;
+
+#define PyCoro_CheckExact(op) (Py_TYPE(op) == &PyCoro_Type)
+PyObject *_PyCoro_GetAwaitableIter(PyObject *o);
+PyAPI_FUNC(PyObject *) PyCoro_New(struct _frame *,
+ PyObject *name, PyObject *qualname);
+#endif
+
+#undef _PyGenObject_HEAD
#ifdef __cplusplus
}
diff -r af793c7580f1 Include/opcode.h
--- a/Include/opcode.h Wed Jun 17 10:09:24 2015 -0500
+++ b/Include/opcode.h Wed Jun 17 12:46:52 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 af793c7580f1 Lib/_collections_abc.py
--- a/Lib/_collections_abc.py Wed Jun 17 10:09:24 2015 -0500
+++ b/Lib/_collections_abc.py Wed Jun 17 12:46:52 2015 -0400
@@ -52,6 +52,12 @@
## misc ##
mappingproxy = type(type.__dict__)
generator = type((lambda: (yield))())
+## coroutine ##
+async def _coro(): pass
+_coro = _coro()
+coroutine = type(_coro)
+_coro.close() # Prevent ResourceWarning
+del _coro
### ONE-TRICK PONIES ###
@@ -78,17 +84,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)
@@ -159,6 +160,9 @@
return NotImplemented
+Coroutine.register(coroutine)
+
+
class AsyncIterable(metaclass=ABCMeta):
__slots__ = ()
diff -r af793c7580f1 Lib/opcode.py
--- a/Lib/opcode.py Wed Jun 17 10:09:24 2015 -0500
+++ b/Lib/opcode.py Wed Jun 17 12:46:52 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 af793c7580f1 Lib/test/test_coroutines.py
--- a/Lib/test/test_coroutines.py Wed Jun 17 10:09:24 2015 -0500
+++ b/Lib/test/test_coroutines.py Wed Jun 17 12:46:52 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
@@ -221,7 +239,7 @@
with silence_coro_gc(), self.assertRaisesRegex(
TypeError,
- "cannot 'yield from' a coroutine object from a generator"):
+ "cannot 'yield from' a coroutine object in a non-coroutine generator"):
list(foo())
@@ -244,6 +262,51 @@
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_func_11(self):
+ async def func(): pass
+ coro = func()
+ # Test that PyCoro_Type and _PyCoroWrapper_Type types were properly
+ # initialized
+ self.assertIn('__await__', dir(coro))
+ self.assertIn('__iter__', dir(coro.__await__()))
+ self.assertIn('coroutine_wrapper', repr(coro.__await__()))
+ coro.close() # avoid RuntimeWarning
+
def test_await_1(self):
async def foo():
@@ -262,6 +325,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 af793c7580f1 Lib/test/test_types.py
--- a/Lib/test/test_types.py Wed Jun 17 10:09:24 2015 -0500
+++ b/Lib/test/test_types.py Wed Jun 17 12:46:52 2015 -0400
@@ -1205,10 +1205,23 @@
def test_wrong_func(self):
@types.coroutine
def foo():
- pass
- with self.assertRaisesRegex(TypeError,
- 'callable wrapped .* non-coroutine'):
- foo()
+ return 'spam'
+ self.assertEqual(foo(), 'spam')
+
+ 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:
@@ -1236,7 +1249,7 @@
def foo():
return gen
self.assertIs(foo().__await__(), gen)
-
+ self.assertTrue(isinstance(foo(), collections.abc.Coroutine))
with self.assertRaises(AttributeError):
foo().gi_code
diff -r af793c7580f1 Lib/types.py
--- a/Lib/types.py Wed Jun 17 10:09:24 2015 -0500
+++ b/Lib/types.py Wed Jun 17 12:46:52 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
@@ -195,11 +204,14 @@
class GeneratorWrapper:
def __init__(self, gen):
self.__wrapped__ = gen
- self.send = gen.send
- self.throw = gen.throw
- self.close = gen.close
self.__name__ = getattr(gen, '__name__', None)
self.__qualname__ = getattr(gen, '__qualname__', None)
+ def send(self, val):
+ return self.__wrapped__.send(val)
+ def throw(self, *args):
+ return self.__wrapped__.throw(*args)
+ def close(self):
+ return self.__wrapped__.close()
@property
def gi_code(self):
return self.__wrapped__.gi_code
@@ -213,20 +225,17 @@
return next(self.__wrapped__)
def __iter__(self):
return self.__wrapped__
- __await__ = __iter__
+ def __await__(self):
+ return self.__wrapped__
@_functools.wraps(func)
def wrapped(*args, **kwargs):
coro = func(*args, **kwargs)
- if coro.__class__ is GeneratorType:
+ if coro.__class__ is CoroutineType:
+ return coro
+ if (coro.__class__ is GeneratorType or
+ isinstance(coro, _collections_abc.Generator)):
return GeneratorWrapper(coro)
- # slow checks
- if not isinstance(coro, _collections_abc.Coroutine):
- if isinstance(coro, _collections_abc.Generator):
- return GeneratorWrapper(coro)
- raise TypeError(
- 'callable wrapped with types.coroutine() returned '
- 'non-coroutine: {!r}'.format(coro))
return coro
return wrapped
diff -r af793c7580f1 Objects/genobject.c
--- a/Objects/genobject.c Wed Jun 17 10:09:24 2015 -0500
+++ b/Objects/genobject.c Wed Jun 17 12:46:52 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("