Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fdbeb2b

Browse files
committed
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.
1 parent 2ab5b09 commit fdbeb2b

10 files changed

Lines changed: 92 additions & 39 deletions

File tree

Doc/library/collections.abc.rst

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,11 @@ ABC Inherits from Abstract Methods Mixin
162162
:class:`~collections.abc.Coroutine` ABC are all instances of this ABC.
163163

164164
.. note::
165-
In CPython, generator-based coroutines are *awaitables*, even though
166-
they do not have an :meth:`__await__` method. This ABC
167-
implements an :meth:`~class.__instancecheck__` method to make them
168-
instances of itself.
165+
In CPython, generator-based coroutines (generators decorated with
166+
:func:`types.coroutine` or :func:`asyncio.coroutine`) are
167+
*awaitables*, even though they do not have an :meth:`__await__` method.
168+
Using ``isinstance(gencoro, Awaitable)`` for them will return ``False``.
169+
Use :func:`inspect.isawaitable` to detect them.
169170

170171
.. versionadded:: 3.5
171172

@@ -179,10 +180,11 @@ ABC Inherits from Abstract Methods Mixin
179180
:class:`Awaitable`. See also the definition of :term:`coroutine`.
180181

181182
.. note::
182-
In CPython, generator-based coroutines are *awaitables* and *coroutines*,
183-
even though they do not have an :meth:`__await__` method. This ABC
184-
implements an :meth:`~class.__instancecheck__` method to make them
185-
instances of itself.
183+
In CPython, generator-based coroutines (generators decorated with
184+
:func:`types.coroutine` or :func:`asyncio.coroutine`) are
185+
*awaitables*, even though they do not have an :meth:`__await__` method.
186+
Using ``isinstance(gencoro, Coroutine)`` for them will return ``False``.
187+
Use :func:`inspect.isawaitable` to detect them.
186188

187189
.. versionadded:: 3.5
188190

Doc/library/inspect.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,25 @@ attributes:
310310
.. versionadded:: 3.5
311311

312312

313+
.. function:: isawaitable(object)
314+
315+
Return true if the object can be used in :keyword:`await` expression.
316+
317+
Can also be used to distinguish generator-based coroutines from regular
318+
generators::
319+
320+
def gen():
321+
yield
322+
@types.coroutine
323+
def gen_coro():
324+
yield
325+
326+
assert not isawaitable(gen())
327+
assert isawaitable(gen_coro())
328+
329+
.. versionadded:: 3.5
330+
331+
313332
.. function:: istraceback(object)
314333

315334
Return true if the object is a traceback.

Doc/whatsnew/3.5.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -532,8 +532,9 @@ inspect
532532
* New argument ``follow_wrapped`` for :func:`inspect.signature`.
533533
(Contributed by Yury Selivanov in :issue:`20691`.)
534534

535-
* New :func:`~inspect.iscoroutine` and :func:`~inspect.iscoroutinefunction`
536-
functions. (Contributed by Yury Selivanov in :issue:`24017`.)
535+
* New :func:`~inspect.iscoroutine`, :func:`~inspect.iscoroutinefunction`
536+
and :func:`~inspect.isawaitable` functions. (Contributed by
537+
Yury Selivanov in :issue:`24017`.)
537538

538539
* New :func:`~inspect.getcoroutinelocals` and :func:`~inspect.getcoroutinestate`
539540
functions. (Contributed by Yury Selivanov in :issue:`24400`.)

Lib/_collections_abc.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,7 @@ def __subclasshook__(cls, C):
8181
return NotImplemented
8282

8383

84-
class _AwaitableMeta(ABCMeta):
85-
86-
def __instancecheck__(cls, instance):
87-
# This hook is needed because we can't add
88-
# '__await__' method to generator objects, and
89-
# we can't register GeneratorType on Awaitable.
90-
# NB: 0x100 = CO_ITERABLE_COROUTINE
91-
# (We don't want to import 'inspect' module, as
92-
# a dependency for 'collections.abc')
93-
if (instance.__class__ is generator and
94-
instance.gi_code.co_flags & 0x100):
95-
return True
96-
return super().__instancecheck__(instance)
97-
98-
99-
class Awaitable(metaclass=_AwaitableMeta):
84+
class Awaitable(metaclass=ABCMeta):
10085

10186
__slots__ = ()
10287

Lib/inspect.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@ def iscoroutine(object):
207207
"""Return true if the object is a coroutine."""
208208
return isinstance(object, types.CoroutineType)
209209

210+
def isawaitable(object):
211+
"""Return true is object can be passed to an ``await`` expression."""
212+
return (isinstance(object, types.CoroutineType) or
213+
isinstance(object, types.GeneratorType) and
214+
object.gi_code.co_flags & CO_ITERABLE_COROUTINE or
215+
isinstance(object, collections.abc.Awaitable))
216+
210217
def istraceback(object):
211218
"""Return true if the object is a traceback.
212219

Lib/test/test_collections.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -511,8 +511,10 @@ def __await__(self):
511511
self.assertTrue(issubclass(type(x), Awaitable))
512512

513513
c = coro()
514-
self.assertIsInstance(c, Awaitable)
515-
c.close() # awoid RuntimeWarning that coro() was not awaited
514+
# Iterable coroutines (generators with CO_ITERABLE_COROUTINE
515+
# flag don't have '__await__' method, hence can't be instances
516+
# of Awaitable. Use inspect.isawaitable to detect them.
517+
self.assertNotIsInstance(c, Awaitable)
516518

517519
c = new_coro()
518520
self.assertIsInstance(c, Awaitable)
@@ -559,8 +561,10 @@ def __await__(self):
559561
self.assertTrue(issubclass(type(x), Awaitable))
560562

561563
c = coro()
562-
self.assertIsInstance(c, Coroutine)
563-
c.close() # awoid RuntimeWarning that coro() was not awaited
564+
# Iterable coroutines (generators with CO_ITERABLE_COROUTINE
565+
# flag don't have '__await__' method, hence can't be instances
566+
# of Coroutine. Use inspect.isawaitable to detect them.
567+
self.assertNotIsInstance(c, Coroutine)
564568

565569
c = new_coro()
566570
self.assertIsInstance(c, Coroutine)

Lib/test/test_inspect.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,29 @@ def test_iscoroutine(self):
151151

152152
coro.close(); gen_coro.close() # silence warnings
153153

154+
def test_isawaitable(self):
155+
def gen(): yield
156+
self.assertFalse(inspect.isawaitable(gen()))
157+
158+
coro = coroutine_function_example(1)
159+
gen_coro = gen_coroutine_function_example(1)
160+
161+
self.assertTrue(inspect.isawaitable(coro))
162+
self.assertTrue(inspect.isawaitable(gen_coro))
163+
164+
class Future:
165+
def __await__():
166+
pass
167+
self.assertTrue(inspect.isawaitable(Future()))
168+
self.assertFalse(inspect.isawaitable(Future))
169+
170+
class NotFuture: pass
171+
not_fut = NotFuture()
172+
not_fut.__await__ = lambda: None
173+
self.assertFalse(inspect.isawaitable(not_fut))
174+
175+
coro.close(); gen_coro.close() # silence warnings
176+
154177
def test_isroutine(self):
155178
self.assertTrue(inspect.isroutine(mod.spam))
156179
self.assertTrue(inspect.isroutine([].count))

Lib/test/test_types.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,19 @@ def foo(): return gen
14471447
with self.assertRaisesRegex(Exception, 'ham'):
14481448
wrapper.throw(Exception, Exception('ham'))
14491449

1450+
def test_returning_itercoro(self):
1451+
@types.coroutine
1452+
def gen():
1453+
yield
1454+
1455+
gencoro = gen()
1456+
1457+
@types.coroutine
1458+
def foo():
1459+
return gencoro
1460+
1461+
self.assertIs(foo(), gencoro)
1462+
14501463
def test_genfunc(self):
14511464
def gen(): yield
14521465
self.assertIs(types.coroutine(gen), gen)
@@ -1457,9 +1470,6 @@ def gen(): yield
14571470
g = gen()
14581471
self.assertTrue(g.gi_code.co_flags & inspect.CO_ITERABLE_COROUTINE)
14591472
self.assertFalse(g.gi_code.co_flags & inspect.CO_COROUTINE)
1460-
self.assertIsInstance(g, collections.abc.Coroutine)
1461-
self.assertIsInstance(g, collections.abc.Awaitable)
1462-
g.close() # silence warning
14631473

14641474
self.assertIs(types.coroutine(gen), gen)
14651475

Lib/types.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,12 @@ def coroutine(func):
241241
@_functools.wraps(func)
242242
def wrapped(*args, **kwargs):
243243
coro = func(*args, **kwargs)
244-
if coro.__class__ is CoroutineType:
245-
# 'coro' is a native coroutine object.
244+
if (coro.__class__ is CoroutineType or
245+
coro.__class__ is GeneratorType and coro.gi_code.co_flags & 0x100):
246+
# 'coro' is a native coroutine object or an iterable coroutine
246247
return coro
247-
if (coro.__class__ is GeneratorType or
248-
(isinstance(coro, _collections_abc.Generator) and
249-
not isinstance(coro, _collections_abc.Coroutine))):
248+
if (isinstance(coro, _collections_abc.Generator) and
249+
not isinstance(coro, _collections_abc.Coroutine)):
250250
# 'coro' is either a pure Python generator iterator, or it
251251
# implements collections.abc.Generator (and does not implement
252252
# collections.abc.Coroutine).

Misc/NEWS

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ Core and Builtins
2525
uses collections.abc.Coroutine, it's intended to test for pure 'async def'
2626
coroutines only; add new opcode: GET_YIELD_FROM_ITER; fix generators wrapper
2727
used in types.coroutine to be instance of collections.abc.Generator;
28-
inspect.isawaitable was removed (use collections.abc.Awaitable).
28+
collections.abc.Awaitable and collections.abc.Coroutine can no longer
29+
be used to detect generator-based coroutines--use inspect.isawaitable
30+
instead.
2931

3032
- Issue #24450: Add gi_yieldfrom to generators and cr_await to coroutines.
3133
Contributed by Benno Leslie and Yury Selivanov.

0 commit comments

Comments
 (0)