diff -r af793c7580f1 Doc/c-api/concrete.rst
--- a/Doc/c-api/concrete.rst Wed Jun 17 10:09:24 2015 -0500
+++ b/Doc/c-api/concrete.rst Thu Jun 18 16:50:43 2015 -0400
@@ -112,5 +112,6 @@
weakref.rst
capsule.rst
gen.rst
+ coro.rst
datetime.rst
diff -r af793c7580f1 Doc/c-api/coro.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/Doc/c-api/coro.rst Thu Jun 18 16:50:43 2015 -0400
@@ -0,0 +1,40 @@
+.. highlightlang:: c
+
+.. _coro-objects:
+
+Coroutine Objects
+-----------------
+
+Coroutine objects are what functions declared with an ``async`` keyword
+return.
+
+
+.. c:type:: PyCoroObject
+
+ The C structure used for coroutine objects.
+
+ .. versionadded:: 3.5
+
+
+.. c:var:: PyTypeObject PyCoro_Type
+
+ The type object corresponding to coroutine objects.
+
+ .. versionadded:: 3.5
+
+
+.. c:function:: int PyCoro_CheckExact(PyObject *ob)
+
+ Return true if *ob*'s type is *PyCoro_Type*; *ob* must not be *NULL*.
+
+ .. versionadded:: 3.5
+
+
+.. c:function:: PyObject* PyCoro_New(PyFrameObject *frame, PyObject *name, PyObject *qualname)
+
+ Create and return a new coroutine object based on the *frame* object,
+ with ``__name__`` and ``__qualname__`` set to *name* and *qualname*.
+ A reference to *frame* is stolen by this function. *frame* must not be
+ *NULL*.
+
+ .. versionadded:: 3.5
diff -r af793c7580f1 Doc/c-api/gen.rst
--- a/Doc/c-api/gen.rst Wed Jun 17 10:09:24 2015 -0500
+++ b/Doc/c-api/gen.rst Thu Jun 18 16:50:43 2015 -0400
@@ -7,7 +7,7 @@
Generator objects are what Python uses to implement generator iterators. They
are normally created by iterating over a function that yields values, rather
-than explicitly calling :c:func:`PyGen_New`.
+than explicitly calling :c:func:`PyGen_New` or :c:func:`PyGen_NewWithQualName`.
.. c:type:: PyGenObject
@@ -20,19 +20,25 @@
The type object corresponding to generator objects
-.. c:function:: int PyGen_Check(ob)
+.. c:function:: int PyGen_Check(PyObject *ob)
Return true if *ob* is a generator object; *ob* must not be *NULL*.
-.. c:function:: int PyGen_CheckExact(ob)
+.. c:function:: int PyGen_CheckExact(PyObject *ob)
- Return true if *ob*'s type is *PyGen_Type* is a generator object; *ob* must not
- be *NULL*.
+ Return true if *ob*'s type is *PyGen_Type*; *ob* must not be *NULL*.
.. c:function:: PyObject* PyGen_New(PyFrameObject *frame)
- Create and return a new generator object based on the *frame* object. A
- reference to *frame* is stolen by this function. The parameter must not be
+ Create and return a new generator object based on the *frame* object.
+ A reference to *frame* is stolen by this function. The parameter must not be
*NULL*.
+
+.. c:function:: PyObject* PyGen_NewWithQualName(PyFrameObject *frame, PyObject *name, PyObject *qualname)
+
+ Create and return a new generator object based on the *frame* object,
+ with ``__name__`` and ``__qualname__`` set to *name* and *qualname*.
+ A reference to *frame* is stolen by this function. *frame* must not be
+ *NULL*.
diff -r af793c7580f1 Doc/data/refcounts.dat
--- a/Doc/data/refcounts.dat Wed Jun 17 10:09:24 2015 -0500
+++ b/Doc/data/refcounts.dat Thu Jun 18 16:50:43 2015 -0400
@@ -491,6 +491,12 @@
PyGen_New:PyObject*::+1:
PyGen_New:PyFrameObject*:frame:0:
+PyGen_NewWithQualName:PyObject*::+1:
+PyGen_NewWithQualName:PyFrameObject*:frame:0:
+
+PyCoro_New:PyObject*::+1:
+PyCoro_New:PyFrameObject*:frame:0:
+
Py_InitModule:PyObject*::0:
Py_InitModule:const char*:name::
Py_InitModule:PyMethodDef[]:methods::
diff -r af793c7580f1 Doc/library/dis.rst
--- a/Doc/library/dis.rst Wed Jun 17 10:09:24 2015 -0500
+++ b/Doc/library/dis.rst Thu Jun 18 16:50:43 2015 -0400
@@ -346,6 +346,14 @@
Implements ``TOS = iter(TOS)``.
+.. opcode:: GET_YIELD_FROM_ITER
+
+ If ``TOS`` is a :term:`generator` or :term:`coroutine` object it is left
+ as is. Otherwise, implements ``TOS = iter(TOS)``.
+
+ .. versionadded:: 3.5
+
+
**Binary operations**
Binary operations remove the top of the stack (TOS) and the second top-most
diff -r af793c7580f1 Doc/library/types.rst
--- a/Doc/library/types.rst Wed Jun 17 10:09:24 2015 -0500
+++ b/Doc/library/types.rst Thu Jun 18 16:50:43 2015 -0400
@@ -90,6 +90,14 @@
generator function.
+.. data:: CoroutineType
+
+ The type of :term:`coroutine` objects, produced by calling a
+ function defined with an :keyword:`async def` keyword.
+
+ .. versionadded:: 3.5
+
+
.. data:: CodeType
.. index:: builtin: compile
diff -r af793c7580f1 Doc/whatsnew/3.5.rst
--- a/Doc/whatsnew/3.5.rst Wed Jun 17 10:09:24 2015 -0500
+++ b/Doc/whatsnew/3.5.rst Thu Jun 18 16:50:43 2015 -0400
@@ -734,6 +734,9 @@
* New :func:`~types.coroutine` function. (Contributed by Yury Selivanov
in :issue:`24017`.)
+* New :class:`~types.CoroutineType`. (Contributed by Yury Selivanov
+ in :issue:`24400`.)
+
urllib
------
@@ -1101,6 +1104,7 @@
the :attr:`__module__` attribute. Would be an AttributeError in future.
(:issue:`20204`)
-* As part of PEP 492 implementation, ``tp_reserved`` slot of
+* As part of :pep:`492` implementation, ``tp_reserved`` slot of
:c:type:`PyTypeObject` was replaced with a
- :c:member:`PyTypeObject.tp_as_async` slot.
+ :c:member:`PyTypeObject.tp_as_async` slot. Refer to :ref:`coro-objects` for
+ new types, structures and functions.
diff -r af793c7580f1 Include/genobject.h
--- a/Include/genobject.h Wed Jun 17 10:09:24 2015 -0500
+++ b/Include/genobject.h Thu Jun 18 16:50:43 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 Thu Jun 18 16:50:43 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 Thu Jun 18 16:50:43 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):
-
+ if (instance.__class__ is generator and
+ 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 Thu Jun 18 16:50:43 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 Thu Jun 18 16:50:43 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))
@@ -136,7 +157,7 @@
raise StopIteration
with self.assertRaisesRegex(
- RuntimeError, "generator raised StopIteration"):
+ RuntimeError, "coroutine raised StopIteration"):
run_async(foo())
@@ -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,98 @@
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_func_12(self):
+ async def g():
+ i = me.send(None)
+ await foo
+ me = g()
+ with self.assertRaisesRegex(ValueError,
+ "coroutine already executing"):
+ me.send(None)
+
+ def test_func_13(self):
+ async def g():
+ pass
+ with self.assertRaisesRegex(
+ TypeError,
+ "can't send non-None value to a just-started coroutine"):
+
+ g().send('spam')
+
+ def test_func_14(self):
+ @types.coroutine
+ def gen():
+ yield
+ async def coro():
+ try:
+ await gen()
+ except GeneratorExit:
+ await gen()
+ c = coro()
+ c.send(None)
+ with self.assertRaisesRegex(RuntimeError,
+ "coroutine ignored GeneratorExit"):
+ c.close()
+
+ def test_corotype_1(self):
+ ct = types.CoroutineType
+ self.assertIn('into coroutine', ct.send.__doc__)
+ self.assertIn('inside coroutine', ct.close.__doc__)
+ self.assertIn('in coroutine', ct.throw.__doc__)
+ self.assertIn('of the coroutine', ct.__dict__['__name__'].__doc__)
+ self.assertIn('of the coroutine', ct.__dict__['__qualname__'].__doc__)
+ self.assertEqual(ct.__name__, 'coroutine')
+
+ async def f(): pass
+ c = f()
+ self.assertIn('coroutine object', repr(c))
+ c.close()
+
def test_await_1(self):
async def foo():
@@ -262,6 +372,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 Thu Jun 18 16:50:43 2015 -0400
@@ -1205,10 +1205,28 @@
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_code = foo.__code__
+ foo_flags = foo.__code__.co_flags
+ decorated_foo = types.coroutine(foo)
+ self.assertIs(foo, decorated_foo)
+ self.assertEqual(foo.__code__.co_flags, foo_flags)
+ self.assertIs(decorated_foo.__code__, foo_code)
+
+ foo_coro = foo()
+ @types.coroutine
+ def bar(): return foo_coro
+ coro = bar()
+ self.assertIs(foo_coro, coro)
+ self.assertEqual(coro.gi_code.co_flags, foo_flags)
+ coro.close()
def test_duck_coro(self):
class CoroLike:
@@ -1221,6 +1239,23 @@
@types.coroutine
def foo():
return coro
+ self.assertIs(foo(), coro)
+ self.assertIs(foo().__await__(), coro)
+
+ def test_duck_corogen(self):
+ class CoroGenLike:
+ def send(self): pass
+ def throw(self): pass
+ def close(self): pass
+ def __await__(self): return self
+ def __iter__(self): return self
+ def __next__(self): pass
+
+ coro = CoroGenLike()
+ @types.coroutine
+ def foo():
+ return coro
+ self.assertIs(foo(), coro)
self.assertIs(foo().__await__(), coro)
def test_duck_gen(self):
@@ -1236,7 +1271,7 @@
def foo():
return gen
self.assertIs(foo().__await__(), gen)
-
+ self.assertTrue(isinstance(foo(), collections.abc.Coroutine))
with self.assertRaises(AttributeError):
foo().gi_code
@@ -1259,7 +1294,13 @@
self.assertFalse(isinstance(gen(), collections.abc.Coroutine))
self.assertFalse(isinstance(gen(), collections.abc.Awaitable))
- self.assertIs(types.coroutine(gen), gen)
+ gen_code = gen.__code__
+ decorated_gen = types.coroutine(gen)
+ self.assertIs(decorated_gen, gen)
+ self.assertIsNot(decorated_gen.__code__, gen_code)
+
+ decorated_gen2 = types.coroutine(decorated_gen)
+ self.assertIs(decorated_gen2.__code__, decorated_gen.__code__)
self.assertTrue(gen.__code__.co_flags & inspect.CO_ITERABLE_COROUTINE)
self.assertFalse(gen.__code__.co_flags & inspect.CO_COROUTINE)
diff -r af793c7580f1 Lib/types.py
--- a/Lib/types.py Wed Jun 17 10:09:24 2015 -0500
+++ b/Lib/types.py Thu Jun 18 16:50:43 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
@@ -164,29 +169,33 @@
def coroutine(func):
"""Convert regular generator function to a coroutine."""
- # We don't want to import 'dis' or 'inspect' just for
- # these constants.
- CO_GENERATOR = 0x20
- 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)):
+ if (func.__class__ is FunctionType and
+ getattr(func, '__code__', None).__class__ is 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
+ co_flags = func.__code__.co_flags
+
+ # Check if 'func' is a coroutine function.
+ # (0x180 == CO_COROUTINE | CO_ITERABLE_COROUTINE)
+ if co_flags & 0x180:
+ return func
+
+ # Check if 'func' is a generator function.
+ # (0x20 == CO_GENERATOR)
+ if co_flags & 0x20:
+ # 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 | 0x100, # 0x100 == 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,24 @@
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:
+ # 'coro' is a native coroutine object.
+ return coro
+ if (coro.__class__ is GeneratorType or
+ (isinstance(coro, _collections_abc.Generator) and
+ not isinstance(coro, _collections_abc.Coroutine))):
+ # 'coro' is either a pure Python generator, or it implements
+ # collections.abc.Generator (and does not implement
+ # collections.abc.Coroutine).
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))
+ # 'coro' is either an instance of collections.abc.Coroutine or
+ # some other object -- pass it through.
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 Thu Jun 18 16:50:43 2015 -0400
@@ -86,8 +86,10 @@
PyObject *result;
if (gen->gi_running) {
- PyErr_SetString(PyExc_ValueError,
- "generator already executing");
+ char *msg = "generator already executing";
+ if PyCoro_CheckExact(gen)
+ msg = "coroutine already executing";
+ PyErr_SetString(PyExc_ValueError, msg);
return NULL;
}
if (f == NULL || f->f_stacktop == NULL) {
@@ -99,9 +101,12 @@
if (f->f_lasti == -1) {
if (arg && arg != Py_None) {
- PyErr_SetString(PyExc_TypeError,
- "can't send non-None value to a "
- "just-started generator");
+ char *msg = "can't send non-None value to a "
+ "just-started generator";
+ if PyCoro_CheckExact(gen)
+ msg = "can't send non-None value to a "
+ "just-started coroutine";
+ PyErr_SetString(PyExc_TypeError, msg);
return NULL;
}
} else {
@@ -150,6 +155,9 @@
if (((PyCodeObject *)gen->gi_code)->co_flags &
(CO_FUTURE_GENERATOR_STOP | CO_COROUTINE | CO_ITERABLE_COROUTINE))
{
+ char *msg = "generator raised StopIteration";
+ if PyCoro_CheckExact(gen)
+ msg = "coroutine raised StopIteration";
PyObject *exc, *val, *val2, *tb;
PyErr_Fetch(&exc, &val, &tb);
PyErr_NormalizeException(&exc, &val, &tb);
@@ -157,8 +165,7 @@
PyException_SetTraceback(val, tb);
Py_DECREF(exc);
Py_XDECREF(tb);
- PyErr_SetString(PyExc_RuntimeError,
- "generator raised StopIteration");
+ PyErr_SetString(PyExc_RuntimeError, msg);
PyErr_Fetch(&exc, &val2, &tb);
PyErr_NormalizeException(&exc, &val2, &tb);
Py_INCREF(val);
@@ -288,9 +295,11 @@
PyErr_SetNone(PyExc_GeneratorExit);
retval = gen_send_ex(gen, Py_None, 1);
if (retval) {
+ char *msg = "generator ignored GeneratorExit";
+ if (PyCoro_CheckExact(gen))
+ msg = "coroutine ignored GeneratorExit";
Py_DECREF(retval);
- PyErr_SetString(PyExc_RuntimeError,
- "generator ignored GeneratorExit");
+ PyErr_SetString(PyExc_RuntimeError, msg);
return NULL;
}
if (PyErr_ExceptionMatches(PyExc_StopIteration)
@@ -432,12 +441,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 +497,8 @@
static PyObject *
gen_repr(PyGenObject *gen)
{
- if (PyGen_CheckCoroutineExact(gen)) {
- return PyUnicode_FromFormat("