summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/collections.abc.rst26
-rw-r--r--Lib/_collections_abc.py17
-rw-r--r--Lib/test/test_collections.py90
-rw-r--r--Lib/test/test_functools.py17
-rw-r--r--Misc/NEWS3
5 files changed, 132 insertions, 21 deletions
diff --git a/Doc/library/collections.abc.rst b/Doc/library/collections.abc.rst
index 6463cea..e4b75a0 100644
--- a/Doc/library/collections.abc.rst
+++ b/Doc/library/collections.abc.rst
@@ -45,10 +45,12 @@ ABC Inherits from Abstract Methods Mixin
: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 @@ ABC Inherits from Abstract Methods Mixin
: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 @@ ABC Inherits from Abstract Methods Mixin
``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 Inherits from Abstract Methods Mixin
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 --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py
index 077bde4..f035970 100644
--- a/Lib/_collections_abc.py
+++ b/Lib/_collections_abc.py
@@ -11,7 +11,7 @@ import sys
__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 @@ class Container(metaclass=ABCMeta):
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 @@ class Callable(metaclass=ABCMeta):
### SETS ###
-class Set(Sized, Iterable, Container):
+class Set(Collection):
"""A set is a finite, iterable container.
@@ -570,7 +579,7 @@ MutableSet.register(set)
### MAPPINGS ###
-class Mapping(Sized, Iterable, Container):
+class Mapping(Collection):
__slots__ = ()
@@ -794,7 +803,7 @@ MutableMapping.register(dict)
### SEQUENCES ###
-class Sequence(Sized, Reversible, Container):
+class Sequence(Reversible, Collection):
"""All the operations on a read-only sequence.
diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py
index 6e858c0..f1fb011 100644
--- a/Lib/test/test_collections.py
+++ b/Lib/test/test_collections.py
@@ -21,7 +21,7 @@ from collections import ChainMap
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 @@ class TestOneTrickPonyABCs(ABCTestCase):
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 --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index 06eacfb..40f2234 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -1548,13 +1548,15 @@ class TestSingleDispatch(unittest.TestCase):
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 @@ class TestSingleDispatch(unittest.TestCase):
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 @@ class TestSingleDispatch(unittest.TestCase):
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
diff --git a/Misc/NEWS b/Misc/NEWS
index 63d19a2..091d7ba 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -126,6 +126,9 @@ Core and Builtins
Library
-------
+- Issue 27598: Add Collections to collections.abc.
+ Patch by Ivan Levkivskyi, docs by Neil Girdhar.
+
- Issue #25958: Support "anti-registration" of special methods from
various ABCs, like __hash__, __iter__ or __len__. All these (and
several more) can be set to None in an implementation class and the