我已经开始在我的代码中使用 Zope 接口,到目前为止,它们实际上只是文档。我使用它们来指定类应该拥有的属性,在适当的类中显式地实现它们,并在我期望的地方显式地检查它们。这很好,但如果可能的话,我希望他们做更多的事情,比如实际验证该类是否实现了接口,而不是仅仅验证我说过该类实现了接口。我已经阅读了 zope wiki 几次,但仍然看不到比我目前正在做的更多的接口用途。所以,我的问题是您还能将这些接口用于什么用途,以及如何将它们用于更多用途。
4 回答
在我工作的地方,我们使用接口,以便我们可以使用 ZCA 或Zope 组件架构,这是一个使用Interface
s 制作可交换和可插入组件的完整框架。我们使用 ZCA,以便我们可以应对各种方式的每个客户端的定制,而不必分叉我们的软件或让所有的每个客户端位搞乱主树。不幸的是,Zope wiki 通常很不完整。ZCA 的 pypi 页面上对 ZCA 的大部分功能都有很好但简洁的解释。
我不使用Interface
s 来检查一个类是否实现了给定的所有方法Interface
。从理论上讲,当您向接口添加另一个方法时,这可能很有用,以检查您是否记得将新方法添加到实现该接口的所有类。就个人而言,我更喜欢创建一个新Interface
的而不是修改一个旧的。Interfaces
一旦它们在已经发布到 pypi 或组织的其他部分的鸡蛋中,修改旧的通常是一个非常糟糕的主意。
关于术语的快速说明:类实现 Interface
s,而对象(类的实例)提供 Interface
s。如果你想检查一个Interface
,你要么写ISomething.implementedBy(SomeClass)
要么ISomething.providedBy(some_object)
。
因此,具体到 ZCA 有用的示例。假设我们正在写一篇博客,使用 ZCA 使其模块化。我们将为BlogPost
每个帖子创建一个对象,该对象将提供一个IBlogPost
接口,所有这些都在我们方便的花花公子my.blog
Egg 中定义。我们还将博客的配置存储在BlogConfiguration
提供IBlogConfiguration
. 以此为起点,我们可以实现新功能,而不必完全接触my.blog
。
以下是我们可以使用 ZCA 做的事情的示例列表,而无需更改基础my.blog
鸡蛋。我或我的同事已经在真正的客户项目中完成了所有这些事情(并且发现它们很有用),尽管我们当时没有实施博客。:) 这里的一些用例可以通过其他方式更好地解决,例如打印 CSS 文件。
向所有提供.
BrowserView
_ _ 我可以做一个鸡蛋。该鸡蛋将注册一个 BrowserView 调用,它通过一个Zope 页面模板呈现博客文章,该模板旨在生成打印良好的 HTML。然后会出现在URL 上。browser:page
IBlogPost
my.blog.printable
print
IBlogPost
BrowserView
/path/to/blogpost/@@print
Zope 中的事件订阅机制。假设我想发布 RSS 提要,并且我想提前生成它们,而不是根据请求生成它们。我可以创造一个
my.blog.rss
鸡蛋。在那个鸡蛋中,我会为提供IObjectModified ( )的事件注册一个订阅者zope.lifecycleevent.interfaces.IObjectModified
,在提供IBlogPost
. 每当提供的任何属性发生更改时,都会调用该订阅者IBlogPost
,我可以使用它来更新博客文章应该出现的所有 RSS 提要。在这种情况下,最好在每个修改博客文章
IBlogPostModified
的 s 的末尾发送一个事件,因为在每个单个属性更改时发送一次 - 这可能太频繁了,为了性能。BrowserView
IObjectModified
适配器。适配器实际上是从一个接口“转换”到另一个接口。对于编程语言极客:Zope 适配器在 Python 中实现“开放”多次调度(“开放”是指“您可以从任何鸡蛋添加更多案例”),更具体的接口匹配优先于不太具体的匹配(
Interface
类可以是彼此的子类,这正是您希望它做的事情。)Interface
可以使用非常好的语法调用来自 one 的适配器ISomething(object_to_adapt)
,或者可以通过函数查找适配器zope.component.getAdapter
。来自多个 s 的适配器Interface
必须通过 function 查找zope.component.getMultiAdapter
,这稍微不那么漂亮。对于给定的一组 s,您可以拥有多个适配器,由您在注册适配器时提供
Interface
的字符串进行区分。name
名称默认为""
. 例如,BrowserView
s 实际上是从它们注册的接口和 HTTPRequest 类实现的接口进行适配的适配器。您还可以使用s查找从一个 s 序列注册到另一个 s 的所有适配器,它返回 (name, adapter) 对的序列。这可以用作一种非常好的方式来为插件提供挂钩以将其附加到。Interface
Interface
zope.component.getAdapters( (IAdaptFrom,), IAdaptTo )
假设我想将我所有博客的帖子和配置保存为一个大的 XML 文件。我创建了一个
my.blog.xmldump
鸡蛋,它定义了一个IXMLSegment
,并注册了一个来自IBlogPost
to 的适配器和一个来自 to的IXMLSegment
适配器。我现在可以调用适合我想通过编写序列化的某个对象的任何适配器。IBlogConfiguration
IXMLSegment
IXMLSegment(object_to_serialize)
IXMLSegment
除了my.blog.xmldump
. _ ZCML 有一个特性,当且仅当安装了一些 egg 时,它才能运行特定的指令。我可以使用它来my.blog.rss
注册一个适配器IRSSFeed
,IXMLSegment
如果my.blog.xmldump
碰巧安装了,而不需要my.blog.rss
依赖my.blog.xmldump
.Viewlet
s 就像 littleBrowserView
s,您可以“订阅”页面内的特定位置。我现在不记得所有的细节,但这些对于你想出现在侧边栏中的插件来说非常有用。我不记得他们是基地 Zope 还是 Plone 的一部分。我建议不要使用 Plone,除非你试图解决的问题实际上需要一个真正的 CMS,因为它是一个庞大而复杂的软件,而且它往往有点慢。
无论如何,您不一定真的需要
Viewlet
s,因为BrowserView
s 可以通过在 TAL 表达式中使用 'object/@@some_browser_view' 或使用 来相互调用queryMultiAdapter( (ISomething, IHttpRequest), name='some_browser_view' )
,但无论如何它们都非常好。标记
Interface
s。标记Interface
是不Interface
提供方法和属性的标记。您可以Interface
在运行时使用ISomething.alsoProvidedBy
. 例如,这允许您更改将在特定对象上使用哪些适配器以及BrowserView
将在其上定义哪些适配器。
很抱歉,我没有详细说明能够立即实现每个示例,但每个示例大约需要一篇博文。
您实际上可以测试您的对象或类是否实现了您的接口。为此,您可以使用verify
模块(您通常会在测试中使用它):
>>> 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
接口也可用于设置和测试不变量。您可以在这里找到更多信息:
Zope 接口可以提供一种有用的方法来解耦不应相互依赖的两段代码。
假设我们有一个组件知道如何在模块 a.py 中打印问候语:
>>> class Greeter(object):
... def greet(self):
... print 'Hello'
还有一些需要在模块 b.py 中打印问候语的代码:
>>> Greeter().greet()
'Hello'
这种安排使得在不接触 b.py(可能分布在单独的包中)的情况下很难更换处理问候语的代码。相反,我们可以引入第三个模块 c.py,它定义了一个 IGreeter 接口:
>>> from zope.interface import Interface
>>> class IGreeter(Interface):
... def greet():
... """ Gives a greeting. """
现在我们可以使用它来解耦 a.py 和 b.py。b.py 现在将请求提供 IGreeter 接口的实用程序,而不是实例化 Greeter 类。a.py 将声明 Greeter 类实现了该接口:
(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'
我从未使用过 Zope 接口,但您可能会考虑编写一个metaclass,它在初始化时根据接口检查类的成员,如果未实现方法,则会引发运行时异常。
使用 Python,您没有其他选择。要么有一个“编译”步骤来检查你的代码,要么在运行时动态地检查它。