]> granicus.if.org Git - python/commitdiff
Issue #24400: Resurrect inspect.isawaitable()
authorYury Selivanov <yselivanov@sprymix.com>
Fri, 3 Jul 2015 17:11:35 +0000 (13:11 -0400)
committerYury Selivanov <yselivanov@sprymix.com>
Fri, 3 Jul 2015 17:11:35 +0000 (13:11 -0400)
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.

Doc/library/collections.abc.rst
Doc/library/inspect.rst
Doc/whatsnew/3.5.rst
Lib/_collections_abc.py
Lib/inspect.py
Lib/test/test_collections.py
Lib/test/test_inspect.py
Lib/test/test_types.py
Lib/types.py
Misc/NEWS

index dc2704ee5bb3330f02b2d59cf9ae6de445c6a043..563c1bc4c5314bf4898c778157197d6f6fc3b236 100644 (file)
@@ -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
 
index d21672f75ea1a94f31bb85957f5e2842e2143837..66b92384f0722e7dedb520ad99a1a8d75cb95597 100644 (file)
@@ -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.
index 3239ce537a5dcb733ff4f6794265bbe7ef44b69f..9713a98f36846407711d75bb3deec4ad574bf691 100644 (file)
@@ -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`.)
index ba6a9b8e902577787d1d77728241a182f2f403b1..f89bb6f04b5b1f7cbf2d0555d0d6e4a34221f48f 100644 (file)
@@ -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__ = ()
 
index f48769e514473a5ccdc0ba1cbf1231f453fc4bb9..45679cfc0b5e3a1bfa789d4b62d60941dd8a9d1b 100644 (file)
@@ -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.
 
index ab2b733b037f114b33caeea4e184ca7cef215742..fbaf712aad067a8affadc41ab4845a4d7fd8f878 100644 (file)
@@ -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)
index ab22c7d677544a8e93dcff92ac3679b563c19b57..a02f2e1b6069f2e91d8301495be3b9f5e3c97b70 100644 (file)
@@ -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))
index ba8a1b93ebfcf15c9dfd7598eaeaf7ae24188de8..738588e9efc831512c6f778d24bad4a114f8627c 100644 (file)
@@ -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)
 
index 8c5fc65d2737e65655b847a0877ad6b469b6d63a..48891cd3f60dab5738c8aff627d094a4905bdbb3 100644 (file)
@@ -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).
index 09d79a40cdab47418e23d0c4dfd135cc4928daf1..f440b18f3094c412e82b897ef7457aafa620f267 100644 (file)
--- 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.