From: Guido van Rossum Date: Mon, 4 Apr 2016 17:59:29 +0000 (-0700) Subject: Add collections.Reversible. Patch by Ivan Levkivskyi. Fixes issue #25987. X-Git-Tag: v3.6.0a1~272 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=16ca06b8cb2426b540fdab75914d7cd0f715b7f0;p=python Add collections.Reversible. Patch by Ivan Levkivskyi. Fixes issue #25987. --- diff --git a/Doc/library/collections.abc.rst b/Doc/library/collections.abc.rst index d9b93ad262..608641bad3 100644 --- a/Doc/library/collections.abc.rst +++ b/Doc/library/collections.abc.rst @@ -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 diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 12b5490b66..1bd4b09da1 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -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`. diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index f89bb6f04b..d3375847e2 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -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. diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 4c32e094f9..42024628c5 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -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__) diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 31930fc763..a22d2e6609 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -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