The easiest way to define an interface with the protocols package is to subclass protocols.Interface. Interface does not supply any data or methods of its own, so you are free to define whatever you need. There are two common styles of defining interfaces, illustrated below:
The ``pure'' style emphasizes the interface as seen by the caller, and is not
intended to be subclassed for implementation. Notice that the self
parameter is not included in its method definitions, because self
is not
supplied when calling the methods. The ``ABC'' style, on the other hand,
emphasizes implementation, as it is intended to be subclassed
for that purpose. Therefore, it includes method bodies, even for abstract
methods. Each style has different uses: ``ABC'' is a popular rapid development
style, while the ``pure'' approach has some distinct documentation advantages.
protocols.AbstractBase may be used as a base class for either style, but protocols.Interface is only usable for the "pure" interface style, as it supports the convenience adaptation API (see section 1.1.3).
(Note: both base classes use an explicit metaclass, so keep in mind that if you want to subclass an abstract base for implementation using a different metaclass, you may need to create a third metaclass that combines protocols.AbstractBaseMeta with your desired metaclass.)
Subclassing a subclass of Interface (or AbstractBase) creates a new interface (or ABC) that implies the first interface (or ABC). This means that any object that supports the second interface (or ABC), is considered to implicitly support the first interface (or ABC). For example:
The IReadWriteMapping
interface implies the IReadMapping
interface. Therefore, any object that supports IReadWriteMapping
is
understood to also support the IReadMapping
interface. The reverse,
however, is not true.
Inheritance is only one way to declare that one interface implies another,
however, and its uses are limited. Let's say for example, that some package
A
supplies objects that support IReadWriteMapping
, while package
B
needs objects that support IReadMapping
. But each package
declared its own interface, neither inheriting from the other.
As developers reading the documentation of these interfaces, it is obvious to
us that IReadWriteMapping
implies IReadMapping
, because we
understand what they do. But there is no way for Python to know this, unless
we explicitly state it, like this:
In the above example, we use the protocols declaration API to say that
no adapter is needed to support the B.IReadMapping
interface for
objects that already support the A.IReadWriteMapping
interface.
At this point, if we supply an object that supports IReadWriteMapping
,
to a function that expects an IReadMapping
, it should work, as long as
we call adapt(ob,IReadMapping)
(or IReadMapping(ob)
) first,
or the code we're calling does so.
There are still other ways to declare that one interface implies another. For
example, if the author of our example package B
knew about package A
and its IReadWriteMapping
interface, he or she might have defined
IReadMapping
this way:
This is syntax sugar for creating the interface first, and then using
protocols.declareAdapter(NO_ADAPTER_NEEDED)
. Of course, you can only use
this approach if you are the author of the interface! Otherwise, you must use
declareAdapter() after the fact, as in the previous example.
In later sections, we will begin looking at the protocols declaration APIs - like declareAdapter() and advise() - in more detail. But first, we must look briefly at the interfaces that the protocols package expects from the protocols, adapters, and other objects supplied as parameters to the declaration API.