Я начал использовать интерфейсы Zope в своем коде, и на данный момент они действительно только документация. Я использую их, чтобы указать, какие атрибуты должен обладать класс, явно реализовать их в соответствующих классах и явно проверять их там, где я их ожидаю. Это прекрасно, но я бы хотел, чтобы они делали больше, если это было возможно, например, на самом деле убедитесь, что класс реализовал интерфейс, а не просто подтвердил, что я сказал, что класс реализует интерфейс. Я читал zope wiki пару раз, но до сих пор не вижу гораздо большего использования интерфейсов, чем то, что я сейчас делаю. Итак, мой вопрос заключается в том, что еще вы можете использовать для этих интерфейсов и как их использовать для большего.
Цель интерфейсов Zope?
Ответ 1
Фактически вы можете проверить, реализует ли ваш объект или класс ваш интерфейс.
Для этого вы можете использовать модуль 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
Интерфейсы также могут использоваться для установки и тестирования инвариантов. Вы можете найти более подробную информацию здесь:
Ответ 2
Где я работаю, мы используем интерфейсы, чтобы мы могли использовать ZCA или Zope Component Architecture, которая представляет собой целую структуру для создания компонентов которые можно сменить и подключить с помощью Interface
s. Мы используем ZCA, чтобы мы могли справляться со всеми индивидуальными настройками для каждого клиента, не обязательно отказываясь от нашего программного обеспечения или иметь все из множества клиентских бит, которые испортили основное дерево. К сожалению, вики Zope часто довольно неполны. Там есть хорошее, но краткое объяснение большинства функций ZCA на странице ZCA pypi.
Я не использую Interface
для чего-то вроде проверки того, что класс реализует все методы для данного Interface
. Теоретически это может быть полезно, когда вы добавляете другой метод в интерфейс, чтобы убедиться, что вы запомнили добавить новый метод ко всем классам, реализующим интерфейс. Лично я предпочитаю создать новый Interface
для изменения старого. Изменение старого Interfaces
обычно является очень плохой идеей, когда они находятся в яйцах, которые были выпущены на pypi или в остальную часть вашей организации.
Быстрая заметка по терминологии: классы реализуют Interface
s, а объекты (экземпляры классов) предоставляют Interface
s. Если вы хотите проверить Interface
, вы должны либо написать ISomething.implementedBy(SomeClass)
, либо ISomething.providedBy(some_object)
.
Итак, вплоть до примеров использования ZCA. Представьте, что мы пишем блог, используя ZCA, чтобы сделать его модульным. У нас будет объект BlogPost
для каждого сообщения, который предоставит интерфейс IBlogPost
, который будет определен в нашем удобном dandy my.blog
яйце. Мы также сохраним конфигурацию блога в BlogConfiguration
объектах, которые предоставляют IBlogConfiguration
. Используя это как отправную точку, мы можем реализовать новые функции, не обязательно прикоснуться к my.blog
вообще.
Ниже приведен список примеров того, что мы можем сделать, используя ZCA, без необходимости изменять базовое яйцо my.blog
. Я или мои сотрудники сделали все это (и нашел их полезными) в реальных проектах для клиента, хотя в то время мы не реализовали блоги.:) Некоторые из вариантов использования здесь могут быть лучше решены другими средствами, такими как файл CSS для печати.
-
Добавление дополнительных просмотров (
BrowserView
s, обычно зарегистрированных в ZCML с помощью директивыbrowser:page
) ко всем объектам которые обеспечиваютIBlogPost
. Я мог бы сделать яйцоmy.blog.printable
. Это яйцо зарегистрировало бы BrowserView под названиемprint
дляIBlogPost
, который отображает сообщение в блоге через шаблон страницы Zope, предназначенный для создания HTML, который печатает мило. ТогдаBrowserView
появится в URL/path/to/blogpost/@@print
. -
Механизм подписки на события в Zope. Скажем, я хочу публиковать RSS-каналы, и я хочу их генерировать заранее, а не по запросу. Я мог бы создать яйцо
my.blog.rss
. В этом яйце я зарегистрировал подписчика на события, которые предоставляют IObjectModified (zope.lifecycleevent.interfaces.IObjectModified
), на объектах, которые предоставляютIBlogPost
. Этот подписчик получит вызов каждый раз, когда атрибут изменится на все, предоставляяIBlogPost
, и я мог бы использовать его для обновления всех RSS-каналов, которые должны появляться в блоге.В этом случае было бы лучше иметь событие
IBlogPostModified
, которое отправляется в конце каждого изBrowserView
, которые изменяют записи в блогах, посколькуIObjectModified
отправляется один раз при каждом изменении атрибута, который может быть слишком часто для эффективности. -
Адаптеры. Адаптеры эффективно "отливают" от одного интерфейса к другому. Для программистов-язычников: адаптеры Zope реализуют "открытую" множественную отправку в Python ( "open" я имею в виду "вы можете добавить больше случаев из любого яйца" ), при этом более конкретные совпадения интерфейса имеют приоритет над менее конкретными совпадениями (
Interface
классы могут быть подклассами друг друга, и это делает именно то, что вы надеялись бы сделать.)Адаптеры из одного
Interface
могут быть вызваны с очень приятным синтаксисом,ISomething(object_to_adapt)
или могут быть просмотрены с помощью функцииzope.component.getAdapter
. Адаптеры из несколькихInterface
должны просматриваться с помощью функцииzope.component.getMultiAdapter
, которая немного менее симпатична.У вас может быть несколько адаптеров для заданного набора
Interface
s, отличающихся строкойname
, которую вы предоставляете при регистрации адаптера. Имя по умолчанию равно""
. Например,BrowserView
на самом деле являются адаптерами, которые адаптируются к интерфейсу, на котором они зарегистрированы, и интерфейсу, который реализует класс HTTPRequest. Вы также можете найти все адаптеры, зарегистрированные из одной последовательностиInterface
, в другуюInterface
, используяzope.component.getAdapters( (IAdaptFrom,), IAdaptTo )
, которая возвращает последовательность пар (имя, адаптер). Это можно использовать в качестве очень приятного способа для создания привязок для подключаемых модулей.Скажем, я хотел сохранить все сообщения и настройки своего блога как один большой XML файл. Я создаю яйцо
my.blog.xmldump
, которое определяетIXMLSegment
, и регистрирует адаптер отIBlogPost
доIXMLSegment
и адаптер отIBlogConfiguration
доIXMLSegment
. Теперь я могу назвать любой адаптер подходящим для некоторого объекта, который я хочу сериализовать, написавIXMLSegment(object_to_serialize)
.Я мог бы добавить еще несколько адаптеров от других вещей до
IXMLSegment
из яиц, кромеmy.blog.xmldump
. ZCML имеет функцию, в которой он может запускать определенную директиву тогда и только тогда, когда установлено какое-либо яйцо. Я мог бы использовать это, чтобыmy.blog.rss
зарегистрировал адаптер отIRSSFeed
доIXMLSegment
, если iffmy.blog.xmldump
будет установлен, не делаяmy.blog.rss
зависеть отmy.blog.xmldump
. -
Viewlet
похожи на маленькиеBrowserView
, что вы можете "подписаться" на определенное место внутри страницы. Я не могу вспомнить все подробности прямо сейчас, но они очень хороши для таких вещей, как плагины, которые вы хотите отображать на боковой панели.Я не могу вспомнить, являются ли они частью базы Zope или Plone. Я бы рекомендовал не использовать Plone, если проблема, которую вы пытаетесь решить, на самом деле нуждается в реальной CMS, поскольку это большой и сложный программный продукт, и он имеет тенденцию быть медленным.
В любом случае вам необязательно
Viewlet
, так какBrowserView
может вызывать друг друга либо с помощью 'object/@@some_browser_view' в выражении TAL, либо с помощьюqueryMultiAdapter( (ISomething, IHttpRequest), name='some_browser_view' )
, но они довольно приятно независимо. -
Маркер
Interface
s. МаркерInterface
- этоInterface
, который не предоставляет никаких методов и атрибутов. Вы можете добавить маркерInterface
к любому объекту во время выполнения, используяISomething.alsoProvidedBy
. Это позволяет, например, изменять, какие адаптеры будут использоваться на конкретном объекте и какиеBrowserView
будут определены на нем.
Я извиняюсь, что я недостаточно подробно остановился на возможности реализовать каждый из этих примеров сразу, но каждый из них займет примерно один блог.
Ответ 3
Интерфейсы 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. Вместо экземпляра класса Greeter, b.py теперь попросит утилиту, обеспечивающую интерфейс IGreeter. И 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'
Ответ 4
Я никогда не использовал интерфейсы Zope, но вы можете подумать о написании metaclass, который при инициализации проверяет членов класса на интерфейс и повышает исключение во время выполнения, если метод не реализован.
С Python у вас нет других опций. У вас есть шаг "компиляции", который проверяет ваш код или динамически проверяет его во время выполнения.