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.