1.1.2 Protocols and Interfaces

Many languages and systems provide ways of defining interfaces that
components provide or require. Some mechanisms are purely for documentation,
others are used at runtime to obtain or verify an implementation. Typically,
interfaces are formal, intended for compiler-verified static type checking.

As a dynamic language, Python more often uses a looser notion of interface,
known as a protocol. While protocols are often very precisely
specified, their intended audience is a human reader or developer, not a
compiler or automated verification tool.

Automated verification tools, however, usually extract a high overhead cost
from developers. The Java language, for example, requires that all methods
of an interface be defined by a class that claims to implement the
interface, even if those methods are never used in the program being
compiled! And yet, the more important dynamic behavior of the
interface at runtime is not captured or verifiable by the compiler, so written
documentation for human readers is still required!

In the Python language, the primary uses for objects representing protocols
or interfaces are at runtime, rather than at compile time. Typically, such
objects are used to ask for an implementation of the interface, or supplied
by an object to claim that it provides an implementation of that interface.

In principle, any Python object may be used as a protocol object.
However, for a variety of practical reasons, it is best that protocol objects
be hashable and comparable. That is, protocol objects should be usable as
dictionary keys.

This still allows for a wide variety of protocol object implementations,
however. One might assign meaning to the number 42, for example, as
referring to some hypothetical ``hitchhiker'' protocol. More realistically,
the Microsoft COM framework uses UUIDs (Universally Unique Identifiers) to
identify interfaces. UUIDs can be represented as Python strings, and thus
are usable as protocol objects.

But a simple string or number is often not very useful as a protocol
object. Aside from the issue of how to assign strings or numbers to
protocols, these passive protocol objects cannot do anything, and by
themselves they document nothing.

There are thus two more common approaches to creating protocol objects in
Python: classes (such as abstract base classes or ``ABCs''), and
interface objects. Interface objects are typically also defined using Python
class statements, but use a custom metaclass to create an object
that may not be usable in the same ways as a ``real'' Python class. Many
Python frameworks (such as Twisted, Zope, and this package) provide their own
framework-specific implementations of this ``interface object'' approach.

Since classes and most interface object implementations can be used as
dictionary keys, and because their Python source code can serve as (or
be converted to) useful documentation, both of these approaches are viable
ways to create protocol objects usable with the protocols package.

In addition, inheriting from a class or interface objects is a simple way to
define implication relationships between protocol objects. Inheriting from a
protocol to create a new protocol means that the new protocol implies
the old protocol. That is, any implementation or adaptation to the new
protocol, is implied to be usable in a place where the old protocol was
required. (We will have more to say about direct and adapted implication
relationships later on, in section 1.1.7.)

At this point, we still haven't described any mechanisms for making adapters
available, or declaring what protocols are supported by a class or object.
To do that, we need to define two additional kinds of protocol objects, that
have more specialized abilities.

An adapting protocol is a protocol object that is potentially able to
adapt components to support the protocol it represents, or at least to
recognize that a component supports (or claims to support) the protocol. To
do this, an adapting protocol must have an __adapt__ method, as
will be described in section 1.1.3. (Often, this method
can be added to an existing class, or patched into an interface object
implementation.)

An open protocol is an adapting protocol that is also capable of
accepting adapter declarations, and managing its implication relationships
with other protocols. Open protocols can be used with this package's
protocol declaration API, as long as they implement (or can be adapted to)
the IOpenProtocol interface, as will be described in section
1.1.5.

Notice that the concepts of protocol objects, adapting protocols, and open
protocols are themselves ``protocols''. The protocols package supplies
three interface objects that symbolize these concepts: IProtocol,
IAdaptingProtocol, and IOpenProtocol, respectively. Just as
the English phrases represent the concepts in this text, the interface objects
represent these concepts at runtime.

Whether a protocol object is as simple as a string, or as complex as an
IOpenProtocol, it can be used to request that a component provide
(or be adaptable to) the protocol that it symbolizes. In the next section,
we'll look at how to make such a request, and how the different kinds of
protocol objects participate (or not) in fulfilling such requests.