Skip to content

Commit 62fa613

Browse files
authored
bpo-45024 and bpo-23864: Document how interface testing works with the collections ABCs (GH-28218)
1 parent 7944307 commit 62fa613

File tree

2 files changed

+168
-63
lines changed

2 files changed

+168
-63
lines changed

Doc/library/collections.abc.rst

+164-63
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
.. testsetup:: *
1616

17-
from collections import *
17+
from collections.abc import *
1818
import itertools
1919
__name__ = '<doctest>'
2020

@@ -24,6 +24,86 @@ This module provides :term:`abstract base classes <abstract base class>` that
2424
can be used to test whether a class provides a particular interface; for
2525
example, whether it is hashable or whether it is a mapping.
2626

27+
An :func:`issubclass` or :func:`isinstance` test for an interface works in one
28+
of three ways.
29+
30+
1) A newly written class can inherit directly from one of the
31+
abstract base classes. The class must supply the required abstract
32+
methods. The remaining mixin methods come from inheritance and can be
33+
overridden if desired. Other methods may be added as needed:
34+
35+
.. testcode::
36+
37+
class C(Sequence): # Direct inheritance
38+
def __init__(self): ... # Extra method not required by the ABC
39+
def __getitem__(self, index): ... # Required abstract method
40+
def __len__(self): ... # Required abstract method
41+
def count(self, value): ... # Optionally override a mixin method
42+
43+
.. doctest::
44+
45+
>>> issubclass(C, Sequence)
46+
True
47+
>>> isinstance(C(), Sequence)
48+
True
49+
50+
2) Existing classes and built-in classes can be registered as "virtual
51+
subclasses" of the ABCs. Those classes should define the full API
52+
including all of the abstract methods and all of the mixin methods.
53+
This lets users rely on :func:`issubclass` or :func:`isinstance` tests
54+
to determine whether the full interface is supported. The exception to
55+
this rule is for methods that are automatically inferred from the rest
56+
of the API:
57+
58+
.. testcode::
59+
60+
class D: # No inheritance
61+
def __init__(self): ... # Extra method not required by the ABC
62+
def __getitem__(self, index): ... # Abstract method
63+
def __len__(self): ... # Abstract method
64+
def count(self, value): ... # Mixin method
65+
def index(self, value): ... # Mixin method
66+
67+
Sequence.register(D) # Register instead of inherit
68+
69+
.. doctest::
70+
71+
>>> issubclass(D, Sequence)
72+
True
73+
>>> isinstance(D(), Sequence)
74+
True
75+
76+
In this example, class :class:`D` does not need to define
77+
``__contains__``, ``__iter__``, and ``__reversed__`` because the
78+
:ref:`in-operator <comparisons>`, the :term:`iteration <iterable>`
79+
logic, and the :func:`reversed` function automatically fall back to
80+
using ``__getitem__`` and ``__len__``.
81+
82+
3) Some simple interfaces are directly recognizable by the presence of
83+
the required methods (unless those methods have been set to
84+
:const:`None`):
85+
86+
.. testcode::
87+
88+
class E:
89+
def __iter__(self): ...
90+
def __next__(next): ...
91+
92+
.. doctest::
93+
94+
>>> issubclass(E, Iterable)
95+
True
96+
>>> isinstance(E(), Iterable)
97+
True
98+
99+
Complex interfaces do not support this last technique because an
100+
interface is more than just the presence of method names. Interfaces
101+
specify semantics and relationships between methods that cannot be
102+
inferred solely from the presence of specific method names. For
103+
example, knowing that a class supplies ``__getitem__``, ``__len__``, and
104+
``__iter__`` is insufficient for distinguishing a :class:`Sequence` from
105+
a :class:`Mapping`.
106+
27107

28108
.. _collections-abstract-base-classes:
29109

@@ -34,67 +114,86 @@ The collections module offers the following :term:`ABCs <abstract base class>`:
34114

35115
.. tabularcolumns:: |l|L|L|L|
36116

37-
========================== ====================== ======================= ====================================================
38-
ABC Inherits from Abstract Methods Mixin Methods
39-
========================== ====================== ======================= ====================================================
40-
:class:`Container` ``__contains__``
41-
:class:`Hashable` ``__hash__``
42-
:class:`Iterable` ``__iter__``
43-
:class:`Iterator` :class:`Iterable` ``__next__`` ``__iter__``
44-
:class:`Reversible` :class:`Iterable` ``__reversed__``
45-
:class:`Generator` :class:`Iterator` ``send``, ``throw`` ``close``, ``__iter__``, ``__next__``
46-
:class:`Sized` ``__len__``
47-
:class:`Callable` ``__call__``
48-
:class:`Collection` :class:`Sized`, ``__contains__``,
49-
:class:`Iterable`, ``__iter__``,
50-
:class:`Container` ``__len__``
51-
52-
:class:`Sequence` :class:`Reversible`, ``__getitem__``, ``__contains__``, ``__iter__``, ``__reversed__``,
53-
:class:`Collection` ``__len__`` ``index``, and ``count``
54-
55-
:class:`MutableSequence` :class:`Sequence` ``__getitem__``, Inherited :class:`Sequence` methods and
56-
``__setitem__``, ``append``, ``reverse``, ``extend``, ``pop``,
57-
``__delitem__``, ``remove``, and ``__iadd__``
58-
``__len__``,
59-
``insert``
60-
61-
:class:`ByteString` :class:`Sequence` ``__getitem__``, Inherited :class:`Sequence` methods
62-
``__len__``
63-
64-
:class:`Set` :class:`Collection` ``__contains__``, ``__le__``, ``__lt__``, ``__eq__``, ``__ne__``,
65-
``__iter__``, ``__gt__``, ``__ge__``, ``__and__``, ``__or__``,
66-
``__len__`` ``__sub__``, ``__xor__``, and ``isdisjoint``
67-
68-
:class:`MutableSet` :class:`Set` ``__contains__``, Inherited :class:`Set` methods and
69-
``__iter__``, ``clear``, ``pop``, ``remove``, ``__ior__``,
70-
``__len__``, ``__iand__``, ``__ixor__``, and ``__isub__``
71-
``add``,
72-
``discard``
73-
74-
:class:`Mapping` :class:`Collection` ``__getitem__``, ``__contains__``, ``keys``, ``items``, ``values``,
75-
``__iter__``, ``get``, ``__eq__``, and ``__ne__``
76-
``__len__``
77-
78-
:class:`MutableMapping` :class:`Mapping` ``__getitem__``, Inherited :class:`Mapping` methods and
79-
``__setitem__``, ``pop``, ``popitem``, ``clear``, ``update``,
80-
``__delitem__``, and ``setdefault``
81-
``__iter__``,
82-
``__len__``
83-
84-
85-
:class:`MappingView` :class:`Sized` ``__len__``
86-
:class:`ItemsView` :class:`MappingView`, ``__contains__``,
87-
:class:`Set` ``__iter__``
88-
:class:`KeysView` :class:`MappingView`, ``__contains__``,
89-
:class:`Set` ``__iter__``
90-
:class:`ValuesView` :class:`MappingView`, ``__contains__``, ``__iter__``
91-
:class:`Collection`
92-
:class:`Awaitable` ``__await__``
93-
:class:`Coroutine` :class:`Awaitable` ``send``, ``throw`` ``close``
94-
:class:`AsyncIterable` ``__aiter__``
95-
:class:`AsyncIterator` :class:`AsyncIterable` ``__anext__`` ``__aiter__``
96-
:class:`AsyncGenerator` :class:`AsyncIterator` ``asend``, ``athrow`` ``aclose``, ``__aiter__``, ``__anext__``
97-
========================== ====================== ======================= ====================================================
117+
============================== ====================== ======================= ====================================================
118+
ABC Inherits from Abstract Methods Mixin Methods
119+
============================== ====================== ======================= ====================================================
120+
:class:`Container` [1]_ ``__contains__``
121+
:class:`Hashable` [1]_ ``__hash__``
122+
:class:`Iterable` [1]_ [2]_ ``__iter__``
123+
:class:`Iterator` [1]_ :class:`Iterable` ``__next__`` ``__iter__``
124+
:class:`Reversible` [1]_ :class:`Iterable` ``__reversed__``
125+
:class:`Generator` [1]_ :class:`Iterator` ``send``, ``throw`` ``close``, ``__iter__``, ``__next__``
126+
:class:`Sized` [1]_ ``__len__``
127+
:class:`Callable` [1]_ ``__call__``
128+
:class:`Collection` [1]_ :class:`Sized`, ``__contains__``,
129+
:class:`Iterable`, ``__iter__``,
130+
:class:`Container` ``__len__``
131+
132+
:class:`Sequence` :class:`Reversible`, ``__getitem__``, ``__contains__``, ``__iter__``, ``__reversed__``,
133+
:class:`Collection` ``__len__`` ``index``, and ``count``
134+
135+
:class:`MutableSequence` :class:`Sequence` ``__getitem__``, Inherited :class:`Sequence` methods and
136+
``__setitem__``, ``append``, ``reverse``, ``extend``, ``pop``,
137+
``__delitem__``, ``remove``, and ``__iadd__``
138+
``__len__``,
139+
``insert``
140+
141+
:class:`ByteString` :class:`Sequence` ``__getitem__``, Inherited :class:`Sequence` methods
142+
``__len__``
143+
144+
:class:`Set` :class:`Collection` ``__contains__``, ``__le__``, ``__lt__``, ``__eq__``, ``__ne__``,
145+
``__iter__``, ``__gt__``, ``__ge__``, ``__and__``, ``__or__``,
146+
``__len__`` ``__sub__``, ``__xor__``, and ``isdisjoint``
147+
148+
:class:`MutableSet` :class:`Set` ``__contains__``, Inherited :class:`Set` methods and
149+
``__iter__``, ``clear``, ``pop``, ``remove``, ``__ior__``,
150+
``__len__``, ``__iand__``, ``__ixor__``, and ``__isub__``
151+
``add``,
152+
``discard``
153+
154+
:class:`Mapping` :class:`Collection` ``__getitem__``, ``__contains__``, ``keys``, ``items``, ``values``,
155+
``__iter__``, ``get``, ``__eq__``, and ``__ne__``
156+
``__len__``
157+
158+
:class:`MutableMapping` :class:`Mapping` ``__getitem__``, Inherited :class:`Mapping` methods and
159+
``__setitem__``, ``pop``, ``popitem``, ``clear``, ``update``,
160+
``__delitem__``, and ``setdefault``
161+
``__iter__``,
162+
``__len__``
163+
164+
165+
:class:`MappingView` :class:`Sized` ``__len__``
166+
:class:`ItemsView` :class:`MappingView`, ``__contains__``,
167+
:class:`Set` ``__iter__``
168+
:class:`KeysView` :class:`MappingView`, ``__contains__``,
169+
:class:`Set` ``__iter__``
170+
:class:`ValuesView` :class:`MappingView`, ``__contains__``, ``__iter__``
171+
:class:`Collection`
172+
:class:`Awaitable` [1]_ ``__await__``
173+
:class:`Coroutine` [1]_ :class:`Awaitable` ``send``, ``throw`` ``close``
174+
:class:`AsyncIterable` [1]_ ``__aiter__``
175+
:class:`AsyncIterator` [1]_ :class:`AsyncIterable` ``__anext__`` ``__aiter__``
176+
:class:`AsyncGenerator` [1]_ :class:`AsyncIterator` ``asend``, ``athrow`` ``aclose``, ``__aiter__``, ``__anext__``
177+
============================== ====================== ======================= ====================================================
178+
179+
180+
.. rubric:: Footnotes
181+
182+
.. [1] These ABCs override :meth:`object.__subclasshook__` to support
183+
testing an interface by verifying the required methods are present
184+
and have not been set to :const:`None`. This only works for simple
185+
interfaces. More complex interfaces require registration or direct
186+
subclassing.
187+
188+
.. [2] Checking ``isinstance(obj, Iterable)`` detects classes that are
189+
registered as :class:`Iterable` or that have an :meth:`__iter__`
190+
method, but it does not detect classes that iterate with the
191+
:meth:`__getitem__` method. The only reliable way to determine
192+
whether an object is :term:`iterable` is to call ``iter(obj)``.
193+
194+
195+
Collections Abstract Base Classes -- Detailed Descriptions
196+
----------------------------------------------------------
98197

99198

100199
.. class:: Container
@@ -244,8 +343,10 @@ ABC Inherits from Abstract Methods Mixin
244343

245344
.. versionadded:: 3.6
246345

346+
Examples and Recipes
347+
--------------------
247348

248-
These ABCs allow us to ask classes or instances if they provide
349+
ABCs allow us to ask classes or instances if they provide
249350
particular functionality, for example::
250351

251352
size = None
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:mod:`collections.abc` documentation has been expanded to explicitly cover
2+
how instance and subclass checks work, with additional doctest examples and
3+
an exhaustive list of ABCs which test membership purely by presence of the
4+
right :term:`special method`\s. Patch by Raymond Hettinger.

0 commit comments

Comments
 (0)