
Abstract base classes
The core of the Abstract Base Class (ABC) definition is defined in a module named abc
. This contains the required decorators and metaclasses to create abstractions. Other classes rely on these definitions.
In Python 3.2, the abstract base classes for collections were buried in collections
. In Python 3.3, however, the abstract base classes have been split into a separate submodule named collections.abc
.
We'll also look at the numbers
module, because it contains ABCs for numeric types. There are abstract base classes for I/O in the io
module too.
We'll focus on Python Version 3.3. The definitions will work very similarly for Python 3.2, but the import
statement will change slightly to reflect the flatter library structure.
An abstract base class has a number of features, as follows:
- Abstract means that these classes don't contain all of the method definitions required to work completely. For it to be a useful subclass, we will need to provide some method definitions.
- Base means that other classes will use it as a superclass.
- An abstract class provides some definitions for method functions. Most importantly, the abstract base classes provide the signatures for the missing method functions. A subclass must provide the right methods to create a concrete class that fits the interface defined by the abstract class.
The features of the abstract base classes include the following ideas:
- We can use them to define a consistent set of base classes for Python's internal classes and our customized application classes.
- We can use them to create some common, reusable abstractions that we can use in our applications.
- We can use them to support the proper inspection of a class to determine what it does. This allows better collaboration among library classes and new classes in our applications. In order to do an inspection properly, it helps to have the formal definition of concepts such as "container" and "number".
Without abstract base classes (that is, in the "bad old days") a container may, or may not, have provided all the features of a Sequence
class consistently. This often leads to a class being almost a sequence or sequence-like. This, in turn, leads to odd inconsistencies and kludgy workarounds for a class that didn't quite provide all the features of a sequence.
With an abstract base class, you can assure that an application's given class will have the advertised features. If it lacks a feature, the presence of an undefined abstract method will make the class unusable for building object instances.
We'll use ABCs in several situations, as follows:
- We'll use ABC's as superclasses when defining our own classes
- We'll use ABC's within a method to confirm that an operation is possible
- We'll use ABC's within a diagnostic message or exception to indicate why an operation can't work
For the first use case, we may write modules with code that looks like the following:
import collections.abc class SomeApplicationClass( collections.abc.Callable ): pass
Our SomeApplicationClass
is defined to be a Callable
class. It must then implement the specific methods required by Callable
, or we will not be able to create an instance.
A function is a concrete example of a Callable
class. The abstraction is a class that defines the __call__()
method. We'll look at Callables
classes in the following section and in Chapter 5, Using Callables and Contexts.
For the second use case, we may write methods with code that looks like the following:
def some_method( self, other ): assert isinstance(other, collections.abc.Iterator)
Our some_method()
requires for the other
argument to be a subclass of Iterator
. If the other
argument can't pass this test, we get an exception. A common alternative to assert
is an if
statement that raises TypeError
, which may be more meaningful. We'll see this in the following section.
For the third use case, we might have something like the following:
try: some_obj.some_method( another ) except AttributeError: warnings.warn( "{0!r} not an Iterator, found {0.__class__.__bases__!r}".format(another) ) raise
In this case, we wrote a diagnostic warning that shows the base classes for a given object. This may help debug the problem with the application design.