Skip to content

Commit 0aa1d5c

Browse files
committed
Improve documentation of ABCs (#6004)
It's a common error to forget to implement an abstract method, and mypy doesn't immediately generate an error. Explain this explicitly in the documentation, since this behavior can be confusing.
1 parent 945ba4f commit 0aa1d5c

File tree

1 file changed

+84
-23
lines changed

1 file changed

+84
-23
lines changed

docs/source/class_basics.rst

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -178,27 +178,35 @@ override has a compatible signature:
178178

179179
.. code-block:: python
180180
181-
class A:
181+
class Base:
182182
def f(self, x: int) -> None:
183183
...
184184
185-
class B(A):
185+
class Derived1(Base):
186186
def f(self, x: str) -> None: # Error: type of 'x' incompatible
187187
...
188188
189-
class C(A):
189+
class Derived2(Base):
190190
def f(self, x: int, y: int) -> None: # Error: too many arguments
191191
...
192192
193-
class D(A):
193+
class Derived3(Base):
194194
def f(self, x: int) -> None: # OK
195195
...
196196
197+
class Derived4(Base):
198+
def f(self, x: float) -> None: # OK: mypy treats int as a subtype of float
199+
...
200+
201+
class Derived5(Base):
202+
def f(self, x: int, y: int = 0) -> None: # OK: accepts more than the base
203+
... # class method
204+
197205
.. note::
198206

199207
You can also vary return types **covariantly** in overriding. For
200-
example, you could override the return type ``object`` with a subtype
201-
such as ``int``. Similarly, you can vary argument types
208+
example, you could override the return type ``Iterable[int]`` with a
209+
subtype such as ``List[int]``. Similarly, you can vary argument types
202210
**contravariantly** -- subclasses can have more general argument types.
203211

204212
You can also override a statically typed method with a dynamically
@@ -213,50 +221,103 @@ effect at runtime:
213221

214222
.. code-block:: python
215223
216-
class A:
224+
class Base:
217225
def inc(self, x: int) -> int:
218226
return x + 1
219227
220-
class B(A):
228+
class Derived(Base):
221229
def inc(self, x): # Override, dynamically typed
222-
return 'hello' # Incompatible with 'A', but no mypy error
230+
return 'hello' # Incompatible with 'Base', but no mypy error
223231
224232
Abstract base classes and multiple inheritance
225233
**********************************************
226234

227235
Mypy supports Python abstract base classes (ABCs). Abstract classes
228236
have at least one abstract method or property that must be implemented
229-
by a subclass. You can define abstract base classes using the
230-
``abc.ABCMeta`` metaclass, and the ``abc.abstractmethod`` and
231-
``abc.abstractproperty`` function decorators. Example:
237+
by any *concrete* (non-abstract) subclass. You can define abstract base
238+
classes using the ``abc.ABCMeta`` metaclass and the ``abc.abstractmethod``
239+
function decorator. Example:
232240

233241
.. code-block:: python
234242
235243
from abc import ABCMeta, abstractmethod
236244
237-
class A(metaclass=ABCMeta):
245+
class Animal(metaclass=ABCMeta):
238246
@abstractmethod
239-
def foo(self, x: int) -> None: pass
247+
def eat(self, food: str) -> None: pass
240248
249+
@property
241250
@abstractmethod
242-
def bar(self) -> str: pass
251+
def can_walk(self) -> bool: pass
252+
253+
class Cat(Animal):
254+
def eat(self, food: str) -> None:
255+
... # Body omitted
243256
244-
class B(A):
245-
def foo(self, x: int) -> None: ...
246-
def bar(self) -> str:
247-
return 'x'
257+
@property
258+
def can_walk(self) -> bool:
259+
return True
248260
249-
a = A() # Error: 'A' is abstract
250-
b = B() # OK
261+
x = Animal() # Error: 'Animal' is abstract due to 'eat' and 'can_walk'
262+
y = Cat() # OK
263+
264+
.. note::
265+
266+
In Python 2.7 you have to use ``@abc.abstractproperty`` to define
267+
an abstract property.
251268

252269
Note that mypy performs checking for unimplemented abstract methods
253270
even if you omit the ``ABCMeta`` metaclass. This can be useful if the
254271
metaclass would cause runtime metaclass conflicts.
255272

273+
Since you can't create instances of ABCs, they are most commonly used in
274+
type annotations. For example, this method accepts arbitrary iterables
275+
containing arbitrary animals (instances of concrete ``Animal``
276+
subclasses):
277+
278+
.. code-block:: python
279+
280+
def feed_all(animals: Iterable[Animal], food: str) -> None:
281+
for animal in animals:
282+
animal.eat(food)
283+
284+
There is one important peculiarity about how ABCs work in Python --
285+
whether a particular class is abstract or not is somewhat implicit.
286+
In the example below, ``Derived`` is treated as an abstract base class
287+
since ``Derived`` inherits an abstract ``f`` method from ``Base`` and
288+
doesn't explicitly implement it. The definition of ``Derived``
289+
generates no errors from mypy, since it's a valid ABC:
290+
291+
.. code-block:: python
292+
293+
from abc import ABCMeta, abstractmethod
294+
295+
class Base(metaclass=ABCMeta):
296+
@abstractmethod
297+
def f(self, x: int) -> None: pass
298+
299+
class Derived(Base): # No error -- Derived is implicitly abstract
300+
def g(self) -> None:
301+
...
302+
303+
Attempting to create an instance of ``Derived`` will be rejected,
304+
however:
305+
306+
.. code-block:: python
307+
308+
d = Derived() # Error: 'Derived' is abstract
309+
310+
.. note::
311+
312+
It's a common error to forget to implement an abstract method.
313+
As shown above, the class definition will not generate an error
314+
in this case, but any attempt to construct an instance will be
315+
flagged as an error.
316+
256317
A class can inherit any number of classes, both abstract and
257318
concrete. As with normal overrides, a dynamically typed method can
258-
implement a statically typed method defined in any base class,
259-
including an abstract method defined in an abstract base class.
319+
override or implement a statically typed method defined in any base
320+
class, including an abstract method defined in an abstract base class.
260321

261322
You can implement an abstract property using either a normal
262323
property or an instance variable.

0 commit comments

Comments
 (0)