1.1.3 adapt() and the Adaptation Protocol

Component adaptation is the central focus of the protocols package. All of the package's protocol declaration API depends on component adaptation in order to function, and the rest of the package is just there to make it easier for developers to use component adaptation in their frameworks and programs.

Component adaptation is performed by calling the adapt() function, whose design is based largely on the specification presented in PEP 246:

adapt( component, protocol, [, default])

Return an implementation of protocol (a protocol object) for component (any object). The implementation returned may be component, or a wrapper that implements the protocol on its behalf. If no implementation is available, return default. If no default is provided, raise protocols.AdaptationFailure.

The component adaptation process performed by adapt() proceeds in four steps:

  1. If protocol is a class or type, and component is an instance of that class or type, the component is returned unchanged. (This quickly disposes of the most trivial cases).

  2. If component has a __conform__ method, it is called, passing in the protocol. If the method returns a value other than None, it is returned as the result of adapt().

  3. If protocol has an __adapt__ method, it is called, passing in component. If the method returns a value other than None, it is returned as the result of adapt().

  4. Perform default processing as described above, returning default or raising protocols.AdaptationFailure as appropriate.

This four-step process is called the adaptation protocol. Note that it can be useful even in the case where neither the component nor the protocol object are aware that the adaptation protocol exists, and it gracefully degrades to a kind of isinstance() check in that case. However, if either the component or the protocol object has been constructed (or altered) so that it has the appropriate __conform__ or __adapt__ method, then much more meaningful results can be achieved.

Throughout the rest of this document, we will say that a component supports a protocol, if calling adapt(component,protocol) does not raise an error. That is, a component supports a protocol if its __conform__ method or the protocol's __adapt__ method return a non-None value.

This is different from saying that an object provides a protocol. An object provides a protocol if adapt(ob,protocol) is ob. Thus, if an object provides a protocol, it supports the protocol, but an object can also support a protocol by having an adapter that provides the protocol on its behalf.

Now that you know how adapt() works, you can actually make use of it without any of the other tools in the protocols package. Just define your own __conform__ and __adapt__ methods, and off you go!

In practice, however, this is like creating a new kind of Python ``number'' type. That is, it's certainly possible, but can be rather tedious and is perhaps best left to a specialist. For that reason, the protocols package supplies some useful basic protocol types, and a ``declaration API'' that lets you declare how protocols, types, and objects should be adapted to one another. The rest of this document deals with how to use those types and APIs.

You don't need to know about those types and APIs to create your own kinds of protocols or components, just as you don't need to have studied Python's numeric types or math libraries to create a numeric type of your own. But, if you'd like your new types to interoperate well with existing types, and conform to users' expectations of how such a type behaves, it would be a good idea to be familiar with existing implementations, such as the ones described here.