Skip to content

Commit 16ca06b

Browse files
author
Guido van Rossum
committed
Add collections.Reversible. Patch by Ivan Levkivskyi. Fixes issue #25987.
1 parent 9ad7646 commit 16ca06b

5 files changed

Lines changed: 61 additions & 13 deletions

File tree

Doc/library/collections.abc.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,13 @@ ABC Inherits from Abstract Methods Mixin
4040
:class:`Hashable` ``__hash__``
4141
:class:`Iterable` ``__iter__``
4242
:class:`Iterator` :class:`Iterable` ``__next__`` ``__iter__``
43+
:class:`Reversible` :class:`Iterable` ``__reversed__``
4344
:class:`Generator` :class:`Iterator` ``send``, ``throw`` ``close``, ``__iter__``, ``__next__``
4445
:class:`Sized` ``__len__``
4546
:class:`Callable` ``__call__``
4647

4748
:class:`Sequence` :class:`Sized`, ``__getitem__``, ``__contains__``, ``__iter__``, ``__reversed__``,
48-
:class:`Iterable`, ``__len__`` ``index``, and ``count``
49+
:class:`Reversible`, ``__len__`` ``index``, and ``count``
4950
:class:`Container`
5051

5152
:class:`MutableSequence` :class:`Sequence` ``__getitem__``, Inherited :class:`Sequence` methods and
@@ -107,6 +108,10 @@ ABC Inherits from Abstract Methods Mixin
107108
:meth:`~iterator.__next__` methods. See also the definition of
108109
:term:`iterator`.
109110

111+
.. class:: Reversible
112+
113+
ABC for classes that provide the :meth:`__reversed__` method.
114+
110115
.. class:: Generator
111116

112117
ABC for generator classes that implement the protocol defined in

Doc/library/typing.rst

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,10 @@ The module defines the following classes, functions and decorators:
351351

352352
A generic version of the :class:`collections.abc.Iterator`.
353353

354+
.. class:: Reversible(Iterable[T_co])
355+
356+
A generic version of the :class:`collections.abc.Reversible`.
357+
354358
.. class:: SupportsInt
355359

356360
An ABC with one abstract method ``__int__``.
@@ -369,11 +373,6 @@ The module defines the following classes, functions and decorators:
369373
An ABC with one abstract method ``__round__``
370374
that is covariant in its return type.
371375

372-
.. class:: Reversible
373-
374-
An ABC with one abstract method ``__reversed__`` returning
375-
an ``Iterator[T_co]``.
376-
377376
.. class:: Container(Generic[T_co])
378377

379378
A generic version of :class:`collections.abc.Container`.
@@ -394,7 +393,7 @@ The module defines the following classes, functions and decorators:
394393

395394
A generic version of :class:`collections.abc.MutableMapping`.
396395

397-
.. class:: Sequence(Sized, Iterable[T_co], Container[T_co])
396+
.. class:: Sequence(Sized, Reversible[T_co], Container[T_co])
398397

399398
A generic version of :class:`collections.abc.Sequence`.
400399

Lib/_collections_abc.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import sys
1111

1212
__all__ = ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator",
13-
"Hashable", "Iterable", "Iterator", "Generator",
13+
"Hashable", "Iterable", "Iterator", "Generator", "Reversible",
1414
"Sized", "Container", "Callable",
1515
"Set", "MutableSet",
1616
"Mapping", "MutableMapping",
@@ -240,6 +240,25 @@ def __subclasshook__(cls, C):
240240
Iterator.register(zip_iterator)
241241

242242

243+
class Reversible(Iterable):
244+
245+
__slots__ = ()
246+
247+
@abstractmethod
248+
def __reversed__(self):
249+
return NotImplemented
250+
251+
@classmethod
252+
def __subclasshook__(cls, C):
253+
if cls is Reversible:
254+
for B in C.__mro__:
255+
if "__reversed__" in B.__dict__:
256+
if B.__dict__["__reversed__"] is not None:
257+
return True
258+
break
259+
return NotImplemented
260+
261+
243262
class Generator(Iterator):
244263

245264
__slots__ = ()
@@ -794,7 +813,7 @@ def setdefault(self, key, default=None):
794813
### SEQUENCES ###
795814

796815

797-
class Sequence(Sized, Iterable, Container):
816+
class Sequence(Sized, Reversible, Container):
798817

799818
"""All the operations on a read-only sequence.
800819

Lib/test/test_collections.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from collections import ChainMap
2121
from collections import deque
2222
from collections.abc import Awaitable, Coroutine, AsyncIterator, AsyncIterable
23-
from collections.abc import Hashable, Iterable, Iterator, Generator
23+
from collections.abc import Hashable, Iterable, Iterator, Generator, Reversible
2424
from collections.abc import Sized, Container, Callable
2525
from collections.abc import Set, MutableSet
2626
from collections.abc import Mapping, MutableMapping, KeysView, ItemsView
@@ -689,6 +689,31 @@ def __iter__(self):
689689
self.validate_abstract_methods(Iterable, '__iter__')
690690
self.validate_isinstance(Iterable, '__iter__')
691691

692+
def test_Reversible(self):
693+
# Check some non-reversibles
694+
non_samples = [None, 42, 3.14, 1j, dict(), set(), frozenset()]
695+
for x in non_samples:
696+
self.assertNotIsInstance(x, Reversible)
697+
self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
698+
# Check some reversibles
699+
samples = [tuple(), list()]
700+
for x in samples:
701+
self.assertIsInstance(x, Reversible)
702+
self.assertTrue(issubclass(type(x), Reversible), repr(type(x)))
703+
# Check also Mapping, MutableMapping, and Sequence
704+
self.assertTrue(issubclass(Sequence, Reversible), repr(Sequence))
705+
self.assertFalse(issubclass(Mapping, Reversible), repr(Mapping))
706+
self.assertFalse(issubclass(MutableMapping, Reversible), repr(MutableMapping))
707+
# Check direct subclassing
708+
class R(Reversible):
709+
def __iter__(self):
710+
return iter(list())
711+
def __reversed__(self):
712+
return iter(list())
713+
self.assertEqual(list(reversed(R())), [])
714+
self.assertFalse(issubclass(float, R))
715+
self.validate_abstract_methods(Reversible, '__reversed__', '__iter__')
716+
692717
def test_Iterator(self):
693718
non_samples = [None, 42, 3.14, 1j, b"", "", (), [], {}, set()]
694719
for x in non_samples:
@@ -842,14 +867,14 @@ def test_Callable(self):
842867
self.validate_isinstance(Callable, '__call__')
843868

844869
def test_direct_subclassing(self):
845-
for B in Hashable, Iterable, Iterator, Sized, Container, Callable:
870+
for B in Hashable, Iterable, Iterator, Reversible, Sized, Container, Callable:
846871
class C(B):
847872
pass
848873
self.assertTrue(issubclass(C, B))
849874
self.assertFalse(issubclass(int, C))
850875

851876
def test_registration(self):
852-
for B in Hashable, Iterable, Iterator, Sized, Container, Callable:
877+
for B in Hashable, Iterable, Iterator, Reversible, Sized, Container, Callable:
853878
class C:
854879
__hash__ = None # Make sure it isn't hashable by default
855880
self.assertFalse(issubclass(C, B), B.__name__)

Lib/test/test_functools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1516,7 +1516,7 @@ class D(c.defaultdict):
15161516
m = mro(D, bases)
15171517
self.assertEqual(m, [D, c.MutableSequence, c.Sequence,
15181518
c.defaultdict, dict, c.MutableMapping,
1519-
c.Mapping, c.Sized, c.Iterable, c.Container,
1519+
c.Mapping, c.Sized, c.Reversible, c.Iterable, c.Container,
15201520
object])
15211521

15221522
# Container and Callable are registered on different base classes and

0 commit comments

Comments
 (0)