summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYury Selivanov <yselivanov@sprymix.com>2015-07-03 17:11:35 (GMT)
committerYury Selivanov <yselivanov@sprymix.com>2015-07-03 17:11:35 (GMT)
commitfdbeb2b4b67e1e44c96127a06cf1bdf878f4f7ca (patch)
tree249f7190feeef1e18f5c88b5987f6e632193df33
parent2ab5b092e5a82390c236708b7c163a32dfc928a1 (diff)
downloadcpython-fdbeb2b4b67e1e44c96127a06cf1bdf878f4f7ca.zip
cpython-fdbeb2b4b67e1e44c96127a06cf1bdf878f4f7ca.tar.gz
cpython-fdbeb2b4b67e1e44c96127a06cf1bdf878f4f7ca.tar.bz2
Issue #24400: Resurrect inspect.isawaitable()
collections.abc.Awaitable and collections.abc.Coroutine no longer use __instancecheck__ hook to detect generator-based coroutines. inspect.isawaitable() can be used to detect generator-based coroutines and to distinguish them from regular generator objects.
-rw-r--r--Doc/library/collections.abc.rst18
-rw-r--r--Doc/library/inspect.rst19
-rw-r--r--Doc/whatsnew/3.5.rst5
-rw-r--r--Lib/_collections_abc.py17
-rw-r--r--Lib/inspect.py7
-rw-r--r--Lib/test/test_collections.py12
-rw-r--r--Lib/test/test_inspect.py23
-rw-r--r--Lib/test/test_types.py16
-rw-r--r--Lib/types.py10
-rw-r--r--Misc/NEWS4
10 files changed, 92 insertions, 39 deletions
diff --git a/Doc/library/collections.abc.rst b/Doc/library/collections.abc.rst
index dc2704e..563c1bc 100644
--- a/Doc/library/collections.abc.rst
+++ b/Doc/library/collections.abc.rst
@@ -162,10 +162,11 @@ ABC Inherits from Abstract Methods Mixin
:class:`~collections.abc.Coroutine` ABC are all instances of this ABC.
.. note::
- In CPython, generator-based coroutines are *awaitables*, even though
- they do not have an :meth:`__await__` method. This ABC
- implements an :meth:`~class.__instancecheck__` method to make them
- instances of itself.
+ In CPython, generator-based coroutines (generators decorated with
+ :func:`types.coroutine` or :func:`asyncio.coroutine`) are
+ *awaitables*, even though they do not have an :meth:`__await__` method.
+ Using ``isinstance(gencoro, Awaitable)`` for them will return ``False``.
+ Use :func:`inspect.isawaitable` to detect them.
.. versionadded:: 3.5
@@ -179,10 +180,11 @@ ABC Inherits from Abstract Methods Mixin
:class:`Awaitable`. See also the definition of :term:`coroutine`.
.. note::
- In CPython, generator-based coroutines are *awaitables* and *coroutines*,
- even though they do not have an :meth:`__await__` method. This ABC
- implements an :meth:`~class.__instancecheck__` method to make them
- instances of itself.
+ In CPython, generator-based coroutines (generators decorated with
+ :func:`types.coroutine` or :func:`asyncio.coroutine`) are
+ *awaitables*, even though they do not have an :meth:`__await__` method.
+ Using ``isinstance(gencoro, Coroutine)`` for them will return ``False``.
+ Use :func:`inspect.isawaitable` to detect them.
.. versionadded:: 3.5
diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index d21672f..66b9238 100644
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -310,6 +310,25 @@ attributes:
.. versionadded:: 3.5
+.. function:: isawaitable(object)
+
+ Return true if the object can be used in :keyword:`await` expression.
+
+ Can also be used to distinguish generator-based coroutines from regular
+ generators::
+
+ def gen():
+ yield
+ @types.coroutine
+ def gen_coro():
+ yield
+
+ assert not isawaitable(gen())
+ assert isawaitable(gen_coro())
+
+ .. versionadded:: 3.5
+
+
.. function:: istraceback(object)
Return true if the object is a traceback.
diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst
index 3239ce5..9713a98 100644
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -532,8 +532,9 @@ inspect
* New argument ``follow_wrapped`` for :func:`inspect.signature`.
(Contributed by Yury Selivanov in :issue:`20691`.)
-* New :func:`~inspect.iscoroutine` and :func:`~inspect.iscoroutinefunction`
- functions. (Contributed by Yury Selivanov in :issue:`24017`.)
+* New :func:`~inspect.iscoroutine`, :func:`~inspect.iscoroutinefunction`
+ and :func:`~inspect.isawaitable` functions. (Contributed by
+ Yury Selivanov in :issue:`24017`.)
* New :func:`~inspect.getcoroutinelocals` and :func:`~inspect.getcoroutinestate`
functions. (Contributed by Yury Selivanov in :issue:`24400`.)
diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py
index ba6a9b8..f89bb6f 100644
--- a/Lib/_collections_abc.py
+++ b/Lib/_collections_abc.py
@@ -81,22 +81,7 @@ class Hashable(metaclass=ABCMeta):
return NotImplemented
-class _AwaitableMeta(ABCMeta):
-
- def __instancecheck__(cls, instance):
- # This hook is needed because we can't add
- # '__await__' method to generator objects, and
- # we can't register GeneratorType on Awaitable.
- # NB: 0x100 = CO_ITERABLE_COROUTINE
- # (We don't want to import 'inspect' module, as
- # a dependency for 'collections.abc')
- if (instance.__class__ is generator and
- instance.gi_code.co_flags & 0x100):
- return True
- return super().__instancecheck__(instance)
-
-
-class Awaitable(metaclass=_AwaitableMeta):
+class Awaitable(metaclass=ABCMeta):
__slots__ = ()
diff --git a/Lib/inspect.py b/Lib/inspect.py
index f48769e..45679cf 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -207,6 +207,13 @@ def iscoroutine(object):
"""Return true if the object is a coroutine."""
return isinstance(object, types.CoroutineType)
+def isawaitable(object):
+ """Return true is object can be passed to an ``await`` expression."""
+ return (isinstance(object, types.CoroutineType) or
+ isinstance(object, types.GeneratorType) and
+ object.gi_code.co_flags & CO_ITERABLE_COROUTINE or
+ isinstance(object, collections.abc.Awaitable))
+
def istraceback(object):
"""Return true if the object is a traceback.
diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py
index ab2b733..fbaf712 100644
--- a/Lib/test/test_collections.py
+++ b/Lib/test/test_collections.py
@@ -511,8 +511,10 @@ class TestOneTrickPonyABCs(ABCTestCase):
self.assertTrue(issubclass(type(x), Awaitable))
c = coro()
- self.assertIsInstance(c, Awaitable)
- c.close() # awoid RuntimeWarning that coro() was not awaited
+ # Iterable coroutines (generators with CO_ITERABLE_COROUTINE
+ # flag don't have '__await__' method, hence can't be instances
+ # of Awaitable. Use inspect.isawaitable to detect them.
+ self.assertNotIsInstance(c, Awaitable)
c = new_coro()
self.assertIsInstance(c, Awaitable)
@@ -559,8 +561,10 @@ class TestOneTrickPonyABCs(ABCTestCase):
self.assertTrue(issubclass(type(x), Awaitable))
c = coro()
- self.assertIsInstance(c, Coroutine)
- c.close() # awoid RuntimeWarning that coro() was not awaited
+ # Iterable coroutines (generators with CO_ITERABLE_COROUTINE
+ # flag don't have '__await__' method, hence can't be instances
+ # of Coroutine. Use inspect.isawaitable to detect them.
+ self.assertNotIsInstance(c, Coroutine)
c = new_coro()
self.assertIsInstance(c, Coroutine)
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index ab22c7d..a02f2e1 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -151,6 +151,29 @@ class TestPredicates(IsTestBase):
coro.close(); gen_coro.close() # silence warnings
+ def test_isawaitable(self):
+ def gen(): yield
+ self.assertFalse(inspect.isawaitable(gen()))
+
+ coro = coroutine_function_example(1)
+ gen_coro = gen_coroutine_function_example(1)
+
+ self.assertTrue(inspect.isawaitable(coro))
+ self.assertTrue(inspect.isawaitable(gen_coro))
+
+ class Future:
+ def __await__():
+ pass
+ self.assertTrue(inspect.isawaitable(Future()))
+ self.assertFalse(inspect.isawaitable(Future))
+
+ class NotFuture: pass
+ not_fut = NotFuture()
+ not_fut.__await__ = lambda: None
+ self.assertFalse(inspect.isawaitable(not_fut))
+
+ coro.close(); gen_coro.close() # silence warnings
+
def test_isroutine(self):
self.assertTrue(inspect.isroutine(mod.spam))
self.assertTrue(inspect.isroutine([].count))
diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index ba8a1b9..738588e 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -1447,6 +1447,19 @@ class CoroutineTests(unittest.TestCase):
with self.assertRaisesRegex(Exception, 'ham'):
wrapper.throw(Exception, Exception('ham'))
+ def test_returning_itercoro(self):
+ @types.coroutine
+ def gen():
+ yield
+
+ gencoro = gen()
+
+ @types.coroutine
+ def foo():
+ return gencoro
+
+ self.assertIs(foo(), gencoro)
+
def test_genfunc(self):
def gen(): yield
self.assertIs(types.coroutine(gen), gen)
@@ -1457,9 +1470,6 @@ class CoroutineTests(unittest.TestCase):
g = gen()
self.assertTrue(g.gi_code.co_flags & inspect.CO_ITERABLE_COROUTINE)
self.assertFalse(g.gi_code.co_flags & inspect.CO_COROUTINE)
- self.assertIsInstance(g, collections.abc.Coroutine)
- self.assertIsInstance(g, collections.abc.Awaitable)
- g.close() # silence warning
self.assertIs(types.coroutine(gen), gen)
diff --git a/Lib/types.py b/Lib/types.py
index 8c5fc65..48891cd 100644
--- a/Lib/types.py
+++ b/Lib/types.py
@@ -241,12 +241,12 @@ def coroutine(func):
@_functools.wraps(func)
def wrapped(*args, **kwargs):
coro = func(*args, **kwargs)
- if coro.__class__ is CoroutineType:
- # 'coro' is a native coroutine object.
+ if (coro.__class__ is CoroutineType or
+ coro.__class__ is GeneratorType and coro.gi_code.co_flags & 0x100):
+ # 'coro' is a native coroutine object or an iterable coroutine
return coro
- if (coro.__class__ is GeneratorType or
- (isinstance(coro, _collections_abc.Generator) and
- not isinstance(coro, _collections_abc.Coroutine))):
+ if (isinstance(coro, _collections_abc.Generator) and
+ not isinstance(coro, _collections_abc.Coroutine)):
# 'coro' is either a pure Python generator iterator, or it
# implements collections.abc.Generator (and does not implement
# collections.abc.Coroutine).
diff --git a/Misc/NEWS b/Misc/NEWS
index 09d79a4..f440b18 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -25,7 +25,9 @@ Core and Builtins
uses collections.abc.Coroutine, it's intended to test for pure 'async def'
coroutines only; add new opcode: GET_YIELD_FROM_ITER; fix generators wrapper
used in types.coroutine to be instance of collections.abc.Generator;
- inspect.isawaitable was removed (use collections.abc.Awaitable).
+ collections.abc.Awaitable and collections.abc.Coroutine can no longer
+ be used to detect generator-based coroutines--use inspect.isawaitable
+ instead.
- Issue #24450: Add gi_yieldfrom to generators and cr_await to coroutines.
Contributed by Benno Leslie and Yury Selivanov.