Purpose of Zope Interfaces?
I have started using Zope interfaces in my code, and as of now, they are really only documentation. I use them to specify what attributes the class should possess, explicitly implement them in the appropriate classes and explicitly check for them where I expect one. This is fine, but I would like them to do more if possible, such as actually verify that the class has implemented the interface, in开发者_开发技巧stead of just verifying that I have said that the class implements the interface. I have read the zope wiki a couple of times, but still cannot see much more use for interfaces than what I am currently doing. So, my question is what else can you use these interfaces for, and how do you use them for more.
Where I work, we use Interfaces so that we can use ZCA, or the Zope Component Architecture, which is a whole framework for making components that are swappable and pluggable using Interface
s. We use ZCA so that we can cope with all manner of per-client customisations without necessarily having to fork our software or have all of the many per-client bits messing up the main tree. The Zope wiki is often quite incomplete, unfortunately. There's a good-but-terse explanation of most of ZCA's features on its ZCA's pypi page.
I don't use Interface
s for anything like checking that a class implements all the methods for a given Interface
. In theory, that might be useful when you add another method to an interface, to check that you've remembered to add the new method to all of the classes that implement the interface. Personally I strongly prefer to create a new Interface
over modifying an old one. Modifying old Interfaces
is usually a very bad idea once they're in eggs that have been released to pypi or to the rest of your organisation.
A quick note on terminology: classes implement Interface
s, and objects (instances of classes) provide Interface
s. If you want to check for an Interface
, you would either write ISomething.implementedBy(SomeClass)
or ISomething.providedBy(some_object)
.
So, down to examples of where ZCA is useful. Let's pretend that we're writing a blog, using the ZCA to make it modular. We'll have a BlogPost
object for each post, which will provide an IBlogPost
interface, all defined in our handy-dandy my.blog
egg. We'll also store the blog's configuration in BlogConfiguration
objects which provide IBlogConfiguration
. Using this as a starting point, we can implement new features without necessarily having to touch my.blog
at all.
The following is a list of examples of things that we can do by using ZCA, without having to alter the base my.blog
egg. I or my co-workers have done all of these things (and found them useful) on real for-client projects, though we weren't implementing blogs at the time. :) Some of the use cases here could be better solved by other means, such as a print CSS file.
Adding extra views (
BrowserView
s, usually registered in ZCML with thebrowser:page
directive) to all objects which provideIBlogPost
. I could make amy.blog.printable
egg. That egg would register a BrowserView calledprint
forIBlogPost
, which renders the blog post through a Zope Page Template designed to produce HTML that prints nicely. ThatBrowserView
would then appear at the URL/path/to/blogpost/@@print
.The event subscription mechanism in Zope. Say I want to publish RSS feeds, and I want to generate them in advance rather than on request. I could create a
my.blog.rss
egg. In that egg, I'd register a subscriber for events that provide IObjectModified (zope.lifecycleevent.interfaces.IObjectModified
), on objects that provideIBlogPost
. That subscriber would get get called every time an attribute changed on anything providingIBlogPost
, and I could use it to update all the RSS feeds that the blog post should appear in.In this case, it might be better to have an
IBlogPostModified
event that is sent at the end of each of theBrowserView
s that modify blog posts, sinceIObjectModified
gets sent once on every single attribute change - which might be too often for performance's sake.Adapters. Adapters are effectively "casts" from one Interface to another. For programming language geeks: Zope adapters implement "open" multiple-dispatch in Python (by "open" I mean "you can add more cases from any egg"), with more-specific interface matches taking priority over less-specific matches (
Interface
classes can be subclasses of one another, and this does exactly what you'd hope it would do.)Adapters from one
Interface
can be called with a very nice syntax,ISomething(object_to_adapt)
, or can be looked up via the functionzope.component.getAdapter
. Adapters from multipleInterface
s have to be looked up via the functionzope.component.getMultiAdapter
, which is slightly less pretty.You can have more than one adapter for a given set of
Interface
s, differentiated by a stringname
that you provide when registering the adapter. The name defaults to""
. For example,BrowserView
s are actually adapters that adapt from the interface that they're registered on and an interface that the HTTPRequest class implements. You can also look up all of the adapters that are registered from one sequence ofInterface
s to anotherInterface
, usingzope.component.getAdapters( (IAdaptFrom,), IAdaptTo )
, which returns a sequence of (name, adapter) pairs. This can be used as a very nice way to provide hooks for plugins to attach themselves to.Say I wanted to save all my blog's posts and configuration as one big XML file. I create a
my.blog.xmldump
egg which defines anIXMLSegment
, and registers an adapter fromIBlogPost
toIXMLSegment
and an adapter fromIBlogConfiguration
toIXMLSegment
. I can now call whichever adapter is appropriate for some object I want to serialize by writingIXMLSegment(object_to_serialize)
.I could even add more adapters from various other things to
IXMLSegment
from eggs other thanmy.blog.xmldump
. ZCML has a feature where it can run a particular directive if and only if some egg is installed. I could use this to havemy.blog.rss
register an adapter fromIRSSFeed
toIXMLSegment
iffmy.blog.xmldump
happens to be installed, without makingmy.blog.rss
depend onmy.blog.xmldump
.Viewlet
s are like littleBrowserView
s that you can have 'subscribe' to a particular spot inside a page. I can't remember all the details right now but these are very good for things like plugins that you want to appear in a sidebar.I can't remember offhand whether they're part of base Zope or Plone. I would recommend against using Plone unless the problem that you are trying to solve actually needs a real CMS, since it's a big and complicated piece of software and it tends to be kinda slow.
You don't necessarily actually need
Viewlet
s anyway, sinceBrowserView
s can call one another, either by using 'object/@@some_browser_view' in a TAL expression, or by usingqueryMultiAdapter( (ISomething, IHttpRequest), name='some_browser_view' )
, but they're pretty nice regardless.Marker
Interface
s. A markerInterface
is anInterface
that provides no methods and no attributes. You can add a markerInterface
any object at runtime usingISomething.alsoProvidedBy
. This allows you to, for example, alter which adapters will get used on a particular object and whichBrowserView
s will be defined on it.
I apologise that I haven't gone into enough detail to be able to implement each of these examples straight away, but they'd take approximately a blog post each.
You can actually test if your object or class implements your interface.
For that you can use verify
module (you would normally use it in your tests):
>>> from zope.interface import Interface, Attribute, implements
>>> class IFoo(Interface):
... x = Attribute("The X attribute")
... y = Attribute("The Y attribute")
>>> class Foo(object):
... implements(IFoo)
... x = 1
... def __init__(self):
... self.y = 2
>>> from zope.interface.verify import verifyObject
>>> verifyObject(IFoo, Foo())
True
>>> from zope.interface.verify import verifyClass
>>> verifyClass(IFoo, Foo)
True
Interfaces can also be used for setting and testing invariants. You can find more information here:
http://www.muthukadan.net/docs/zca.html#interfaces
Zope interfaces can provide a useful way to decouple two pieces of code that shouldn't depend on each other.
Say we have a component that knows how to print a greeting in module a.py:
>>> class Greeter(object):
... def greet(self):
... print 'Hello'
And some code that needs to print a greeting in module b.py:
>>> Greeter().greet()
'Hello'
This arrangement makes it hard to swap out the code that handles the greeting without touching b.py (which might be distributed in a separate package). Instead, we could introduce a third module c.py which defines an IGreeter interface:
>>> from zope.interface import Interface
>>> class IGreeter(Interface):
... def greet():
... """ Gives a greeting. """
Now we can use this to decouple a.py and b.py. Instead of instantiating a Greeter class, b.py will now ask for a utility providing the IGreeter interface. And a.py will declare that the Greeter class implements that interface:
(a.py)
>>> from zope.interface import implementer
>>> from zope.component import provideUtility
>>> from c import IGreeter
>>> @implementer(IGreeter)
... class Greeter(object):
... def greet(self):
... print 'Hello'
>>> provideUtility(Greeter(), IGreeter)
(b.py)
>>> from zope.component import getUtility
>>> from c import IGreeter
>>> greeter = getUtility(IGreeter)
>>> greeter.greet()
'Hello'
I've never used Zope interfaces, but you might consider writing a metaclass, which on initialization checks the members of the class against the interface, and raises a runtime exception if a method isn't implemented.
With Python you don't have other options. Either have a "compile" step that inspects your code, or dynamically inspect it at runtime.
精彩评论