1.1.6.2 Protocol Declarations for Individual Objects
Description: Protocol Declarations for Individual Objects
Keywords: ref

###
1.1.6.2 Protocol Declarations for Individual Objects

Because objects' behavior usually comes from a class definition, it's not too often that you will declare that an individual object provides or supports an interface, as opposed to making a blanket declaration about an entire class or type of object. But objects like functions and modules do not have a class definition that encompasses their behavior, and classes themselves sometimes provide an interface (e.g. via `classmethod` objects).

So, the declaration API needs to also be able to declare what protocols an individual object (such as a function, module, or class) supports or provides. This is what `adviseObject()` and the `classProvides`/`classDoesNotProvide` keywords of `advise()` do.

In most cases, for an object to be usable with `adviseObject()`, it must support the `IOpenProvider` interface. Since many of the objects one might wish to use with `adviseObject()` (such as modules, functions, and classes) do not directly provide this interface, the `protocols.classic` module supplies and declares an adapter factory tha can adapt most Python objects to support this interface, assuming that they have a `__dict__` attribute.

This default adapter works well for many situations, but it has some limitations you may need to be aware of. First, it works by ``poking'' a new `__conform__` method into the adapted object. If the object already has a `__conform__` method, a `TypeError` will be raised. So, if you need an object to be an `IOpenProvider`, but it has a `__conform__` method, you may want to have its class include `ProviderMixin` among its base classes, so that your objects won't rely on the default adapter for `IOpenProvider`. (See `ProviderMixin` in section 1.1.9 for more on this.)

Both the default adapter and `ProviderMixin` support inheritance of protocol declarations, when the object being adapted is a class or type. In this way, `advise(classProvides=protocols)` declarations (or `adviseObject(someClass,protocols)` calls) are inherited by subclasses. Of course, you can always reject inherited protocol information using `advise(classDoesNotProvide=protocols)` or `adviseObject(newClass,doesNotProvide=protocols)`.

Both the default adapter and `ProviderMixin` work by keeping a mapping of protocols to adapter factories. Keep in mind that this means the protocols and adapter factories will continue to live until your object is garbage collected. Also, that means for your object to be pickleable, all of the protocols and adapter factories used must be pickleable. (This latter requirement can be quite difficult to meet, since composed adapter factories are dynamically created functions at present.)

Note that none of these restrictions apply if you are only using declarations about types and protocols, as opposed to individual objects. (Or if you only make individual-object declarations for functions, modules, and classes.) Also note that if you have some objects that need to dynamically support or not support a protocol on a per-instance basis, then `adviseObject()` is probably not what you want anyway! Instead, give your objects' class a `__conform__()` method that does the right thing when the object is asked to conform to a protocol. `adviseObject()` is really intended for adding metadata to objects that ``don't know any better''.

In general, protocol declarations are a static mechanism: they cannot be changed or removed at will, only successively refined. All protocol declarations made must be consistent with the declarations that have already been made. This makes them unsuitable as a mechanism for dynamic behavior such as supporting a protocol based on an object's current state.

In the next section, we'll look more at the static nature of declarations, and explore what it means to make conflicting (or refining) protocol declarations.