Even people who are materially invested in Zope seem not to really understand Zope 3's "adaptation" pattern very well. I suspect this is mostly because most explanations of the pattern seem to get tripped up on irrelevant details. It's actually a very simple thing. This is an attempt to provide a short explanation without the irrelevancies.
Let's say you want to reuse some code. In particular, you really want to churn. Someone has come up with a churner implmentation named SimpleChurner as a class, and has kindly put it in a library that you can download and use. So you decide to subclass it:
>>> class SimpleChurner(object): ... def churn(self): ... self.churned = True >>> class MyClass(SimpleChurner): ... pass >>> a = MyClass() >>> a.churn()
This works fine, and the code is very simple. But there's some presumptions going on here. You don't control the SimpleChurner class definition. Things change over time. It might do exactly what you want now but it may become the case that the SimpleChurner implementation has (or accretes) too much irrelevant code, or does slightly the wrong thing for your use case. You might just end up changing your MyClass code to deal with some added functionality you need directly, e.g.:
>>> class MyClass(SimpleChurner): ... def churn(self): ... SimpleChurner.churn(self) ... self.creamy = True
Now it's also creamy. The old one wasn't creamy. Awesome. Job done. If you ever need to change how MyClass instances are churned, you just go in and change MyClass. If things in SimpleChurner later get really out of control, you can just stop subclassing from the class and perform all of the functionality yourself in MyClass. But you do need to change MyClass to do either.
There's of course another way that doesn't use subclassing:
>>> class SimpleChurner(object): ... def __init__(self, other): ... self.other = other ... ... def churn(self): ... self.other.churned = True >>> class MyClass(object): ... def churn(self): ... churner = SimpleChurner(self) ... churner.churn() >>> a = MyClass() >>> a.churn()
This works fine too, but if you want the functionality of a Churner in your application, you need to use the single named implementation of a Churner. If you wanted to make things creamy too, and you had a CreamyChurner to handle this, you would end up changing your MyClass code to use it, e.g.:
>>> class CreamyChurner(object): ... def __init__(self, other): ... self.other = other ... ... def churn(self): ... self.other.churned = True ... self.other.creamy = True >>> class MyClass(object): ... def churn(self): ... churner = CreamyChurner(self) ... churner.churn() >>> a = MyClass() >>> a.churn()
That works pretty good. You've still needed to modify MyClass to get the behavior you want, but all's well.
But what if we didn't want to need to modify MyClass in order to
provide new behavior? What if you meant to redistribute MyClass as a
reusable bit of code and wanted people to be able to plug in whatever
kind of churner they wanted without editing your code? What if we
wanted to make the kind of churner that MyClass uses "pluggable"
churners in such a way that the module that contains MyClass needn't
ever be edited? Well, we could make its churn method accept a
churner:
>>> class CreamyChurner(object): ... def __init__(self, other): ... self.other = other ... ... def churn(self): ... self.other.churned = True ... self.other.creamy = True >>> class MyClass(object): ... def churn(self, churner): ... churner(self).churn() >>> a = MyClass() >>> a.churn(CreamyChurner)
Woot! That works just fine. Of course, when we do this, we need to
make its callers pass in the right class to the churn method. And
presumably somewhere in one of the callers we name the CreamyChurner
class by name. So although we no longer need to modify MyClass,
callers need to be modified to use a different churner implementation.
In essence, we've just delegated the problem to our callers.
We can get away from needing to modify the MyClass code and spare its callers at the same time if we use adaptation to perform the same function. The adaptation way:
>>> from zope.interface import implements >>> from zope.interface import Interface >>> from zope.component import provideAdapter >>> class IChurner(Interface): ... def churn(): ... "Churn the context" >>> class IChurned(Interface): ... def churn(): ... "Get churned" >>> class SimpleChurner(object): ... implements(IChurner) ... def __init__(self, context): ... self.context = context ... def churn(self): ... self.context.churned = True >>> class MyClass(object): ... implements(IChurned) ... ... def churn(self): ... churner = IChurner(self) ... churner.churn() >>> provideAdapter(SimpleChurner, [IChurned], IChurner)
The provideAdapter line, in English, says something like "when you
notice that someone is requesting an IChurner, if the thing named
in the lookup is an IChurned, return a SimpleChurner".
This is a simple concept that allows people to create and plug in
custom IChurner implementations. The people doing the overriding just
need to make one simple declaration about which factory
(SimpleChurner) to provide when adapting one interface (IChurned) to
another (IChurner). This declaration is shown above as the
provideAdapter python line but it can also be done via Zope's ZCML
configuration language if you want more "declarativeness" and less
"imperativeness".
Zope's component architecture happens to perform its lookup indirections using "interfaces", which is an abstraction that seems to trip people up. An interface really just a type of marker object that you attach to a class definition that with an "implements" statement which says "I'm this kind of thing". As a bonus, interfaces can help you describe the API of the things you're registering and looking up. They also function as the callables to actually do the lookup. Passing someting that implements IChurned into a call to IChurner will return something that implements the IChurner interface. In the above configuration, that's a SimpleChurner. You can also declare that a class implements an interface by poking at it from the outside, so you don't necessarily need to add the "implements(IChurner)" line to code that has already been written.
This is of course not really rocket science. You could do this with lookup table or some other sort of registry, avoiding the use of interfaces entirely. But the Zope adaptation pattern is just a way to do this where you don't have to go and recreate the registry population and lookup machinery every time you want to do something like this ("do I do it by name? by type?").
Instead of creating your own implementation of a generic lookup facility that allows declarative configuration, you might consider just using Zope's adaptation if you ever need to do such a thing. Just go grab and install zope.component and zope.interface Both can be installed as plain old Python packages or as source eggs.
two points (last example, that uses the adapter):
* couldn't find the definition of IMyClass
I guess that's easy to add
* I needed to call provider with a list [IMyClass]
provideAdapter(SimpleChurner, [IMyClass], IChurner)