36

我已经开始在我的代码中使用 Zope 接口,到目前为止,它们实际上只是文档。我使用它们来指定类应该拥有的属性,在适当的类中显式地实现它们,并在我期望的地方显式地检查它们。这很好,但如果可能的话,我希望他们做更多的事情,比如实际验证该类是否实现了接口,而不是仅仅验证我说过该类实现了接口。我已经阅读了 zope wiki 几次,但仍然看不到比我目前正在做的更多的接口用途。所以,我的问题是您还能将这些接口用于什么用途,以及如何将它们用于更多用途。

4

4 回答 4

52

在我工作的地方,我们使用接口,以便我们可以使用 ZCA 或Zope 组件架构,这是一个使用Interfaces 制作可交换和可插入组件的完整框架。我们使用 ZCA,以便我们可以应对各种方式的每个客户端的定制,而不必分叉我们的软件或让所有的每个客户端位搞乱主树。不幸的是,Zope wiki 通常很不完整。ZCA 的 pypi 页面上对 ZCA 的大部分功能都有很好但简洁的解释。

我不使用Interfaces 来检查一个类是否实现了给定的所有方法Interface。从理论上讲,当您向接口添加另一个方法时,这可能很有用,以检查您是否记得将新方法添加到实现该接口的所有类。就个人而言,我更喜欢创建一个新Interface的而不是修改一个旧的。Interfaces一旦它们在已经发布到 pypi 或组织的其他部分的鸡蛋中,修改旧的通常是一个非常糟糕的主意。

关于术语的快速说明:类实现 Interfaces,而对象(类的实例)提供 Interfaces。如果你想检查一个Interface,你要么写ISomething.implementedBy(SomeClass)要么ISomething.providedBy(some_object)

因此,具体到 ZCA 有用的示例。假设我们正在写一篇博客,使用 ZCA 使其模块化。我们将为BlogPost每个帖子创建一个对象,该对象将提供一个IBlogPost接口,所有这些都在我们方便的花花公子my.blogEgg 中定义。我们还将博客的配置存储在BlogConfiguration提供IBlogConfiguration. 以此为起点,我们可以实现新功能,而不必完全接触my.blog

以下是我们可以使用 ZCA 做的事情的示例列表,而无需更改基础my.blog鸡蛋。我或我的同事已经在真正的客户项目中完成了所有这些事情(并且发现它们很有用),尽管我们当时没有实施博客。:) 这里的一些用例可以通过其他方式更好地解决,例如打印 CSS 文件。

  1. 所有提供. BrowserView_ _ 我可以做一个鸡蛋。该鸡蛋将注册一个 BrowserView 调用,它通过一个Zope 页面模板呈现博客文章,该模板旨在生成打印良好的 HTML。然后会出现在URL 上。browser:pageIBlogPostmy.blog.printableprintIBlogPostBrowserView/path/to/blogpost/@@print

  2. Zope 中的事件订阅机制。假设我想发布 RSS 提要,并且我想提前生成它们,而不是根据请求生成它们。我可以创造一个my.blog.rss鸡蛋。在那个鸡蛋中,我会为提供IObjectModified ( )的事件注册一个订阅者 zope.lifecycleevent.interfaces.IObjectModified,在提供IBlogPost. 每当提供的任何属性发生更改时,都会调用该订阅者IBlogPost,我可以使用它来更新博客文章应该出现的所有 RSS 提要。

    在这种情况下,最好在每个修改博客文章IBlogPostModified的 s 的末尾发送一个事件,因为在每个单个属性更改时发送一次 - 这可能太频繁了,为了性能。BrowserViewIObjectModified

  3. 适配器。适配器实际上是从一个接口“转换”到另一个接口。对于编程语言极客:Zope 适配器在 Python 中实现“开放”多次调度(“开放”是指“您可以从任何鸡蛋添加更多案例”),更具体的接口匹配优先于不太具体的匹配(Interface类可以是彼此的子类,这正是您希望它做的事情。)

    Interface可以使用非常好的语法调用来自 one 的适配器ISomething(object_to_adapt),或者可以通过函数查找适配器zope.component.getAdapter。来自多个 s 的适配器Interface必须通过 function 查找zope.component.getMultiAdapter,这稍微不那么漂亮。

    对于给定的一组 s,您可以拥有多个适配器,由您在注册适配器时提供Interface的字符串进行区分。name名称默认为"". 例如,BrowserViews 实际上是从它们注册的接口和 HTTPRequest 类实现的接口进行适配的适配器。您还可以使用s查找从一个 s 序列注册到另一个 s 的所有适配器,它返回 (name, adapter) 对的序列。这可以用作一种非常好的方式来为插件提供挂钩以将其附加到。InterfaceInterfacezope.component.getAdapters( (IAdaptFrom,), IAdaptTo )

    假设我想将我所有博客的帖子和配置保存为一个大的 XML 文件。我创建了一个my.blog.xmldump鸡蛋,它定义了一个IXMLSegment,并注册了一个来自IBlogPostto 的适配器和一个来自 to的IXMLSegment适配器。我现在可以调用适合我想通过编写序列化的某个对象的任何适配器。IBlogConfigurationIXMLSegmentIXMLSegment(object_to_serialize)

    IXMLSegment除了my.blog.xmldump. _ ZCML 有一个特性,当且仅当安装了一些 egg 时,它才能运行特定的指令。我可以使用它来my.blog.rss注册一个适配器IRSSFeedIXMLSegment如果my.blog.xmldump碰巧安装了,而不需要my.blog.rss依赖my.blog.xmldump.

  4. Viewlets 就像 little BrowserViews,您可以“订阅”页面内的特定位置。我现在不记得所有的细节,但这些对于你想出现在侧边栏中的插件来说非常有用。

    我不记得他们是基地 Zope 还是 Plone 的一部分。我建议不要使用 Plone,除非你试图解决的问题实际上需要一个真正的 CMS,因为它是一个庞大而复杂的软件,而且它往往有点慢。

    无论如何,您不一定真的需要Viewlets,因为BrowserViews 可以通过在 TAL 表达式中使用 'object/@@some_browser_view' 或使用 来相互调用queryMultiAdapter( (ISomething, IHttpRequest), name='some_browser_view' ),但无论如何它们都非常好。

  5. 标记Interfaces。标记Interface是不Interface提供方法和属性的标记。您可以Interface在运行时使用ISomething.alsoProvidedBy. 例如,这允许您更改将在特定对象上使用哪些适配器以及BrowserView将在其上定义哪些适配器。

很抱歉,我没有详细说明能够立即实现每个示例,但每个示例大约需要一篇博文。

于 2010-05-15T16:05:57.020 回答
24

您实际上可以测试您的对象或类是否实现了您的接口。为此,您可以使用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

接口也可用于设置和测试不变量。您可以在这里找到更多信息:

http://www.muthukadan.net/docs/zca.html#interfaces

于 2010-03-26T07:20:22.330 回答
19

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'
于 2010-04-15T01:02:30.680 回答
2

我从未使用过 Zope 接口,但您可能会考虑编写一个metaclass,它在初始化时根据接口检查类的成员,如果未实现方法,则会引发运行时异常。

使用 Python,您没有其他选择。要么有一个“编译”步骤来检查你的代码,要么在运行时动态地检查它。

于 2010-03-26T05:40:51.010 回答