Add collections.Reversible. Patch by Ivan Levkivskyi. Fixes issue #25987.

This commit is contained in:
Guido van Rossum 2016-04-04 10:59:29 -07:00
parent 9ad764601b
commit 16ca06b8cb
5 changed files with 61 additions and 13 deletions

View File

@ -40,12 +40,13 @@ ABC Inherits from Abstract Methods Mixin
:class:`Hashable` ``__hash__``
:class:`Iterable` ``__iter__``
:class:`Iterator` :class:`Iterable` ``__next__`` ``__iter__``
:class:`Reversible` :class:`Iterable` ``__reversed__``
:class:`Generator` :class:`Iterator` ``send``, ``throw`` ``close``, ``__iter__``, ``__next__``
:class:`Sized` ``__len__``
:class:`Callable` ``__call__``
:class:`Sequence` :class:`Sized`, ``__getitem__``, ``__contains__``, ``__iter__``, ``__reversed__``,
:class:`Iterable`, ``__len__`` ``index``, and ``count``
:class:`Reversible`, ``__len__`` ``index``, and ``count``
:class:`Container`
:class:`MutableSequence` :class:`Sequence` ``__getitem__``, Inherited :class:`Sequence` methods and
@ -107,6 +108,10 @@ ABC Inherits from Abstract Methods Mixin
:meth:`~iterator.__next__` methods. See also the definition of
:term:`iterator`.
.. class:: Reversible
ABC for classes that provide the :meth:`__reversed__` method.
.. class:: Generator
ABC for generator classes that implement the protocol defined in

View File

@ -351,6 +351,10 @@ The module defines the following classes, functions and decorators:
A generic version of the :class:`collections.abc.Iterator`.
.. class:: Reversible(Iterable[T_co])
A generic version of the :class:`collections.abc.Reversible`.
.. class:: SupportsInt
An ABC with one abstract method ``__int__``.
@ -369,11 +373,6 @@ The module defines the following classes, functions and decorators:
An ABC with one abstract method ``__round__``
that is covariant in its return type.
.. class:: Reversible
An ABC with one abstract method ``__reversed__`` returning
an ``Iterator[T_co]``.
.. class:: Container(Generic[T_co])
A generic version of :class:`collections.abc.Container`.
@ -394,7 +393,7 @@ The module defines the following classes, functions and decorators:
A generic version of :class:`collections.abc.MutableMapping`.
.. class:: Sequence(Sized, Iterable[T_co], Container[T_co])
.. class:: Sequence(Sized, Reversible[T_co], Container[T_co])
A generic version of :class:`collections.abc.Sequence`.

View File

@ -10,7 +10,7 @@ from abc import ABCMeta, abstractmethod
import sys
__all__ = ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator",
"Hashable", "Iterable", "Iterator", "Generator",
"Hashable", "Iterable", "Iterator", "Generator", "Reversible",
"Sized", "Container", "Callable",
"Set", "MutableSet",
"Mapping", "MutableMapping",
@ -240,6 +240,25 @@ Iterator.register(tuple_iterator)
Iterator.register(zip_iterator)
class Reversible(Iterable):
__slots__ = ()
@abstractmethod
def __reversed__(self):
return NotImplemented
@classmethod
def __subclasshook__(cls, C):
if cls is Reversible:
for B in C.__mro__:
if "__reversed__" in B.__dict__:
if B.__dict__["__reversed__"] is not None:
return True
break
return NotImplemented
class Generator(Iterator):
__slots__ = ()
@ -794,7 +813,7 @@ MutableMapping.register(dict)
### SEQUENCES ###
class Sequence(Sized, Iterable, Container):
class Sequence(Sized, Reversible, Container):
"""All the operations on a read-only sequence.

View File

@ -20,7 +20,7 @@ from collections import UserDict, UserString, UserList
from collections import ChainMap
from collections import deque
from collections.abc import Awaitable, Coroutine, AsyncIterator, AsyncIterable
from collections.abc import Hashable, Iterable, Iterator, Generator
from collections.abc import Hashable, Iterable, Iterator, Generator, Reversible
from collections.abc import Sized, Container, Callable
from collections.abc import Set, MutableSet
from collections.abc import Mapping, MutableMapping, KeysView, ItemsView
@ -689,6 +689,31 @@ class TestOneTrickPonyABCs(ABCTestCase):
self.validate_abstract_methods(Iterable, '__iter__')
self.validate_isinstance(Iterable, '__iter__')
def test_Reversible(self):
# Check some non-reversibles
non_samples = [None, 42, 3.14, 1j, dict(), set(), frozenset()]
for x in non_samples:
self.assertNotIsInstance(x, Reversible)
self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
# Check some reversibles
samples = [tuple(), list()]
for x in samples:
self.assertIsInstance(x, Reversible)
self.assertTrue(issubclass(type(x), Reversible), repr(type(x)))
# Check also Mapping, MutableMapping, and Sequence
self.assertTrue(issubclass(Sequence, Reversible), repr(Sequence))
self.assertFalse(issubclass(Mapping, Reversible), repr(Mapping))
self.assertFalse(issubclass(MutableMapping, Reversible), repr(MutableMapping))
# Check direct subclassing
class R(Reversible):
def __iter__(self):
return iter(list())
def __reversed__(self):
return iter(list())
self.assertEqual(list(reversed(R())), [])
self.assertFalse(issubclass(float, R))
self.validate_abstract_methods(Reversible, '__reversed__', '__iter__')
def test_Iterator(self):
non_samples = [None, 42, 3.14, 1j, b"", "", (), [], {}, set()]
for x in non_samples:
@ -842,14 +867,14 @@ class TestOneTrickPonyABCs(ABCTestCase):
self.validate_isinstance(Callable, '__call__')
def test_direct_subclassing(self):
for B in Hashable, Iterable, Iterator, Sized, Container, Callable:
for B in Hashable, Iterable, Iterator, Reversible, Sized, Container, Callable:
class C(B):
pass
self.assertTrue(issubclass(C, B))
self.assertFalse(issubclass(int, C))
def test_registration(self):
for B in Hashable, Iterable, Iterator, Sized, Container, Callable:
for B in Hashable, Iterable, Iterator, Reversible, Sized, Container, Callable:
class C:
__hash__ = None # Make sure it isn't hashable by default
self.assertFalse(issubclass(C, B), B.__name__)

View File

@ -1516,7 +1516,7 @@ class TestSingleDispatch(unittest.TestCase):
m = mro(D, bases)
self.assertEqual(m, [D, c.MutableSequence, c.Sequence,
c.defaultdict, dict, c.MutableMapping,
c.Mapping, c.Sized, c.Iterable, c.Container,
c.Mapping, c.Sized, c.Reversible, c.Iterable, c.Container,
object])
# Container and Callable are registered on different base classes and