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.