diff -r da47269c6de8 Doc/library/collections.abc.rst --- a/Doc/library/collections.abc.rst Thu Aug 18 21:26:56 2016 +0100 +++ b/Doc/library/collections.abc.rst Fri Aug 19 11:09:56 2016 +0200 @@ -45,10 +45,12 @@ :class:`Generator` :class:`Iterator` ``send``, ``throw`` ``close``, ``__iter__``, ``__next__`` :class:`Sized` ``__len__`` :class:`Callable` ``__call__`` +:class:`Collection` :class:`Sized`, ``__contains__``, + :class:`Iterable`, ``__iter__``, + :class:`Container` ``__len__`` -:class:`Sequence` :class:`Sized`, ``__getitem__``, ``__contains__``, ``__iter__``, ``__reversed__``, - :class:`Reversible`, ``__len__`` ``index``, and ``count`` - :class:`Container` +:class:`Sequence` :class:`Reversible`, ``__getitem__``, ``__contains__``, ``__iter__``, ``__reversed__``, + :class:`Collection` ``__len__`` ``index``, and ``count`` :class:`MutableSequence` :class:`Sequence` ``__getitem__``, Inherited :class:`Sequence` methods and ``__setitem__``, ``append``, ``reverse``, ``extend``, ``pop``, @@ -59,9 +61,9 @@ :class:`ByteString` :class:`Sequence` ``__getitem__``, Inherited :class:`Sequence` methods ``__len__`` -:class:`Set` :class:`Sized`, ``__contains__``, ``__le__``, ``__lt__``, ``__eq__``, ``__ne__``, - :class:`Iterable`, ``__iter__``, ``__gt__``, ``__ge__``, ``__and__``, ``__or__``, - :class:`Container` ``__len__`` ``__sub__``, ``__xor__``, and ``isdisjoint`` +:class:`Set` :class:`Collection` ``__contains__``, ``__le__``, ``__lt__``, ``__eq__``, ``__ne__``, + ``__iter__``, ``__gt__``, ``__ge__``, ``__and__``, ``__or__``, + ``__len__`` ``__sub__``, ``__xor__``, and ``isdisjoint`` :class:`MutableSet` :class:`Set` ``__contains__``, Inherited :class:`Set` methods and ``__iter__``, ``clear``, ``pop``, ``remove``, ``__ior__``, @@ -69,9 +71,9 @@ ``add``, ``discard`` -:class:`Mapping` :class:`Sized`, ``__getitem__``, ``__contains__``, ``keys``, ``items``, ``values``, - :class:`Iterable`, ``__iter__``, ``get``, ``__eq__``, and ``__ne__`` - :class:`Container` ``__len__`` +:class:`Mapping` :class:`Collection` ``__getitem__``, ``__contains__``, ``keys``, ``items``, ``values``, + ``__iter__``, ``get``, ``__eq__``, and ``__ne__`` + ``__len__`` :class:`MutableMapping` :class:`Mapping` ``__getitem__``, Inherited :class:`Mapping` methods and ``__setitem__``, ``pop``, ``popitem``, ``clear``, ``update``, @@ -106,6 +108,12 @@ ABC for classes that provide the :meth:`__iter__` method. See also the definition of :term:`iterable`. +.. class:: Collection + + ABC for sized iterable container classes. + + .. versionadded:: 3.6 + .. class:: Iterator ABC for classes that provide the :meth:`~iterator.__iter__` and diff -r da47269c6de8 Lib/_collections_abc.py --- a/Lib/_collections_abc.py Thu Aug 18 21:26:56 2016 +0100 +++ b/Lib/_collections_abc.py Fri Aug 19 11:09:56 2016 +0200 @@ -11,7 +11,7 @@ __all__ = ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator", "Hashable", "Iterable", "Iterator", "Generator", "Reversible", - "Sized", "Container", "Callable", + "Sized", "Container", "Callable", "Collection", "Set", "MutableSet", "Mapping", "MutableMapping", "MappingView", "KeysView", "ItemsView", "ValuesView", @@ -326,6 +326,15 @@ return _check_methods(C, "__contains__") return NotImplemented +class Collection(Sized, Iterable, Container): + + __slots__ = () + + @classmethod + def __subclasshook__(cls, C): + if cls is Collection: + return _check_methods(C, "__len__", "__iter__", "__contains__") + return NotImplemented class Callable(metaclass=ABCMeta): @@ -345,7 +354,7 @@ ### SETS ### -class Set(Sized, Iterable, Container): +class Set(Collection): """A set is a finite, iterable container. @@ -570,7 +579,7 @@ ### MAPPINGS ### -class Mapping(Sized, Iterable, Container): +class Mapping(Collection): __slots__ = () @@ -794,7 +803,7 @@ ### SEQUENCES ### -class Sequence(Sized, Reversible, Container): +class Sequence(Reversible, Collection): """All the operations on a read-only sequence. diff -r da47269c6de8 Lib/test/test_collections.py --- a/Lib/test/test_collections.py Thu Aug 18 21:26:56 2016 +0100 +++ b/Lib/test/test_collections.py Fri Aug 19 11:09:56 2016 +0200 @@ -21,7 +21,7 @@ from collections import deque from collections.abc import Awaitable, Coroutine, AsyncIterator, AsyncIterable from collections.abc import Hashable, Iterable, Iterator, Generator, Reversible -from collections.abc import Sized, Container, Callable +from collections.abc import Sized, Container, Callable, Collection from collections.abc import Set, MutableSet from collections.abc import Mapping, MutableMapping, KeysView, ItemsView, ValuesView from collections.abc import Sequence, MutableSequence @@ -771,6 +771,94 @@ self.assertFalse(issubclass(RevRevBlocked, Reversible)) self.assertFalse(isinstance(RevRevBlocked(), Reversible)) + def test_Collection(self): + # Check some non-collections + non_collections = [None, 42, 3.14, 1j, lambda x: 2*x] + for x in non_collections: + self.assertNotIsInstance(x, Collection) + self.assertFalse(issubclass(type(x), Collection), repr(type(x))) + # Check some non-collection iterables + non_col_iterables = [_test_gen(), iter(b''), iter(bytearray()), + (x for x in []), dict().values()] + for x in non_col_iterables: + self.assertNotIsInstance(x, Collection) + self.assertFalse(issubclass(type(x), Collection), repr(type(x))) + # Check some collections + samples = [set(), frozenset(), dict(), bytes(), str(), tuple(), + list(), dict().keys(), dict().items()] + for x in samples: + self.assertIsInstance(x, Collection) + self.assertTrue(issubclass(type(x), Collection), repr(type(x))) + # Check also Mapping, MutableMapping, etc. + self.assertTrue(issubclass(Sequence, Collection), repr(Sequence)) + self.assertTrue(issubclass(Mapping, Collection), repr(Mapping)) + self.assertTrue(issubclass(MutableMapping, Collection), + repr(MutableMapping)) + self.assertTrue(issubclass(Set, Collection), repr(Set)) + self.assertTrue(issubclass(MutableSet, Collection), repr(MutableSet)) + self.assertTrue(issubclass(Sequence, Collection), repr(MutableSet)) + # Check direct subclassing + class Col(Collection): + def __iter__(self): + return iter(list()) + def __len__(self): + return 0 + def __contains__(self, item): + return False + class DerCol(Col): pass + self.assertEqual(list(iter(Col())), []) + self.assertFalse(issubclass(list, Col)) + self.assertFalse(issubclass(set, Col)) + self.assertFalse(issubclass(float, Col)) + self.assertEqual(list(iter(DerCol())), []) + self.assertFalse(issubclass(list, DerCol)) + self.assertFalse(issubclass(set, DerCol)) + self.assertFalse(issubclass(float, DerCol)) + self.validate_abstract_methods(Collection, '__len__', '__iter__', + '__contains__') + # Check sized container non-iterable (which is not Collection) etc. + class ColNoIter: + def __len__(self): return 0 + def __contains__(self, item): return False + class ColNoSize: + def __iter__(self): return iter([]) + def __contains__(self, item): return False + class ColNoCont: + def __iter__(self): return iter([]) + def __len__(self): return 0 + self.assertFalse(issubclass(ColNoIter, Collection)) + self.assertFalse(isinstance(ColNoIter(), Collection)) + self.assertFalse(issubclass(ColNoSize, Collection)) + self.assertFalse(isinstance(ColNoSize(), Collection)) + self.assertFalse(issubclass(ColNoCont, Collection)) + self.assertFalse(isinstance(ColNoCont(), Collection)) + # Check None blocking + class SizeBlock: + def __iter__(self): return iter([]) + def __contains__(self): return False + __len__ = None + class IterBlock: + def __len__(self): return 0 + def __contains__(self): return True + __iter__ = None + self.assertFalse(issubclass(SizeBlock, Collection)) + self.assertFalse(isinstance(SizeBlock(), Collection)) + self.assertFalse(issubclass(IterBlock, Collection)) + self.assertFalse(isinstance(IterBlock(), Collection)) + # Check None blocking in subclass + class ColImpl: + def __iter__(self): + return iter(list()) + def __len__(self): + return 0 + def __contains__(self, item): + return False + class NonCol(ColImpl): + __contains__ = None + self.assertFalse(issubclass(NonCol, Collection)) + self.assertFalse(isinstance(NonCol(), Collection)) + + def test_Iterator(self): non_samples = [None, 42, 3.14, 1j, b"", "", (), [], {}, set()] for x in non_samples: diff -r da47269c6de8 Lib/test/test_functools.py --- a/Lib/test/test_functools.py Thu Aug 18 21:26:56 2016 +0100 +++ b/Lib/test/test_functools.py Fri Aug 19 11:09:56 2016 +0200 @@ -1548,13 +1548,15 @@ bases = [c.Sequence, c.MutableMapping, c.Mapping, c.Set] for haystack in permutations(bases): m = mro(dict, haystack) - self.assertEqual(m, [dict, c.MutableMapping, c.Mapping, c.Sized, - c.Iterable, c.Container, object]) + self.assertEqual(m, [dict, c.MutableMapping, c.Mapping, + c.Collection, c.Sized, c.Iterable, + c.Container, object]) bases = [c.Container, c.Mapping, c.MutableMapping, c.OrderedDict] for haystack in permutations(bases): m = mro(c.ChainMap, haystack) self.assertEqual(m, [c.ChainMap, c.MutableMapping, c.Mapping, - c.Sized, c.Iterable, c.Container, object]) + c.Collection, c.Sized, c.Iterable, + c.Container, object]) # If there's a generic function with implementations registered for # both Sized and Container, passing a defaultdict to it results in an @@ -1575,9 +1577,9 @@ bases = [c.MutableSequence, c.MutableMapping] for haystack in permutations(bases): m = mro(D, bases) - self.assertEqual(m, [D, c.MutableSequence, c.Sequence, - c.defaultdict, dict, c.MutableMapping, - c.Mapping, c.Sized, c.Reversible, c.Iterable, c.Container, + self.assertEqual(m, [D, c.MutableSequence, c.Sequence, c.Reversible, + c.defaultdict, dict, c.MutableMapping, c.Mapping, + c.Collection, c.Sized, c.Iterable, c.Container, object]) # Container and Callable are registered on different base classes and @@ -1590,7 +1592,8 @@ for haystack in permutations(bases): m = mro(C, haystack) self.assertEqual(m, [C, c.Callable, c.defaultdict, dict, c.Mapping, - c.Sized, c.Iterable, c.Container, object]) + c.Collection, c.Sized, c.Iterable, + c.Container, object]) def test_register_abc(self): c = collections