Skip to content

Commit 56fc614

Browse files
author
Yury Selivanov
committed
Issue 24315: Make collections.abc.Coroutine derived from Awaitable
1 parent 8fa6d4f commit 56fc614

4 files changed

Lines changed: 67 additions & 40 deletions

File tree

Doc/library/collections.abc.rst

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ ABC Inherits from Abstract Methods Mixin
8282
:class:`Set` ``__iter__``
8383
:class:`ValuesView` :class:`MappingView` ``__contains__``, ``__iter__``
8484
:class:`Awaitable` ``__await__``
85-
:class:`Coroutine` ``send``, ``throw`` ``close``
85+
:class:`Coroutine` :class:`Awaitable` ``send``, ``throw`` ``close``
8686
:class:`AsyncIterable` ``__aiter__``
8787
:class:`AsyncIterator` :class:`AsyncIterable` ``__anext__`` ``__aiter__``
8888
========================== ====================== ======================= ====================================================
@@ -166,10 +166,10 @@ ABC Inherits from Abstract Methods Mixin
166166

167167
ABC for coroutine compatible classes that implement a subset of
168168
generator methods defined in :pep:`342`, namely:
169-
:meth:`~generator.send`, :meth:`~generator.throw` and
170-
:meth:`~generator.close` methods. All :class:`Coroutine` instances
171-
are also instances of :class:`Awaitable`. See also the definition
172-
of :term:`coroutine`.
169+
:meth:`~generator.send`, :meth:`~generator.throw`,
170+
:meth:`~generator.close` methods. :meth:`__await__` must also be
171+
implemented. All :class:`Coroutine` instances are also instances of
172+
:class:`Awaitable`. See also the definition of :term:`coroutine`.
173173

174174
.. versionadded:: 3.5
175175

Lib/_collections_abc.py

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def __subclasshook__(cls, C):
7575
return NotImplemented
7676

7777

78-
class _CoroutineMeta(ABCMeta):
78+
class _AwaitableMeta(ABCMeta):
7979

8080
def __instancecheck__(cls, instance):
8181
# 0x80 = CO_COROUTINE
@@ -92,7 +92,26 @@ def __instancecheck__(cls, instance):
9292
return super().__instancecheck__(instance)
9393

9494

95-
class Coroutine(metaclass=_CoroutineMeta):
95+
class Awaitable(metaclass=_AwaitableMeta):
96+
97+
__slots__ = ()
98+
99+
@abstractmethod
100+
def __await__(self):
101+
yield
102+
103+
@classmethod
104+
def __subclasshook__(cls, C):
105+
if cls is Awaitable:
106+
for B in C.__mro__:
107+
if "__await__" in B.__dict__:
108+
if B.__dict__["__await__"]:
109+
return True
110+
break
111+
return NotImplemented
112+
113+
114+
class Coroutine(Awaitable):
96115

97116
__slots__ = ()
98117

@@ -126,27 +145,19 @@ def close(self):
126145
else:
127146
raise RuntimeError("coroutine ignored GeneratorExit")
128147

129-
130-
class Awaitable(metaclass=_CoroutineMeta):
131-
132-
__slots__ = ()
133-
134-
@abstractmethod
135-
def __await__(self):
136-
yield
137-
138148
@classmethod
139149
def __subclasshook__(cls, C):
140-
if cls is Awaitable:
141-
for B in C.__mro__:
142-
if "__await__" in B.__dict__:
143-
if B.__dict__["__await__"]:
144-
return True
145-
break
150+
if cls is Coroutine:
151+
mro = C.__mro__
152+
for method in ('__await__', 'send', 'throw', 'close'):
153+
for base in mro:
154+
if method in base.__dict__:
155+
break
156+
else:
157+
return NotImplemented
158+
return True
146159
return NotImplemented
147160

148-
Awaitable.register(Coroutine)
149-
150161

151162
class AsyncIterable(metaclass=ABCMeta):
152163

Lib/test/test_asyncio/test_pep492.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,18 +97,14 @@ async def foo(): pass
9797
finally:
9898
f.close() # silence warning
9999

100-
class FakeCoro(collections.abc.Coroutine):
100+
# Test that asyncio.iscoroutine() uses collections.abc.Coroutine
101+
class FakeCoro:
101102
def send(self, value): pass
102103
def throw(self, typ, val=None, tb=None): pass
104+
def close(self): pass
105+
def __await__(self): yield
103106

104-
fc = FakeCoro()
105-
try:
106-
self.assertTrue(asyncio.iscoroutine(fc))
107-
finally:
108-
# To make sure that ABCMeta caches are freed
109-
# from FakeCoro ASAP.
110-
fc = FakeCoro = None
111-
support.gc_collect()
107+
self.assertTrue(asyncio.iscoroutine(FakeCoro()))
112108

113109

114110
if __name__ == '__main__':

Lib/test/test_collections.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,8 @@ def send(self, value):
496496
return value
497497
def throw(self, typ, val=None, tb=None):
498498
super().throw(typ, val, tb)
499+
def __await__(self):
500+
yield
499501

500502
non_samples = [None, int(), gen(), object()]
501503
for x in non_samples:
@@ -515,13 +517,7 @@ def throw(self, typ, val=None, tb=None):
515517
self.assertIsInstance(c, Awaitable)
516518
c.close() # awoid RuntimeWarning that coro() was not awaited
517519

518-
class CoroLike:
519-
def send(self, value):
520-
pass
521-
def throw(self, typ, val=None, tb=None):
522-
pass
523-
def close(self):
524-
pass
520+
class CoroLike: pass
525521
Coroutine.register(CoroLike)
526522
self.assertTrue(isinstance(CoroLike(), Awaitable))
527523
self.assertTrue(issubclass(CoroLike, Awaitable))
@@ -548,6 +544,8 @@ def send(self, value):
548544
return value
549545
def throw(self, typ, val=None, tb=None):
550546
super().throw(typ, val, tb)
547+
def __await__(self):
548+
yield
551549

552550
non_samples = [None, int(), gen(), object(), Bar()]
553551
for x in non_samples:
@@ -567,6 +565,28 @@ def throw(self, typ, val=None, tb=None):
567565
self.assertIsInstance(c, Coroutine)
568566
c.close() # awoid RuntimeWarning that coro() was not awaited
569567

568+
class CoroLike:
569+
def send(self, value):
570+
pass
571+
def throw(self, typ, val=None, tb=None):
572+
pass
573+
def close(self):
574+
pass
575+
def __await__(self):
576+
pass
577+
self.assertTrue(isinstance(CoroLike(), Coroutine))
578+
self.assertTrue(issubclass(CoroLike, Coroutine))
579+
580+
class CoroLike:
581+
def send(self, value):
582+
pass
583+
def close(self):
584+
pass
585+
def __await__(self):
586+
pass
587+
self.assertFalse(isinstance(CoroLike(), Coroutine))
588+
self.assertFalse(issubclass(CoroLike, Coroutine))
589+
570590
def test_Hashable(self):
571591
# Check some non-hashables
572592
non_samples = [bytearray(), list(), set(), dict()]

0 commit comments

Comments
 (0)