Назначение интерфейсов Zope?
Я начал использовать интерфейсы Zope в своем коде, и на данный момент они на самом деле являются только документацией. Я использую их, чтобы указать, какими атрибутами должен обладать класс, явным образом реализовать их в соответствующих классах и явно проверить их там, где я ожидаю. Это нормально, но я бы хотел, чтобы они делали больше, если это возможно, например, фактически проверяли, что класс реализовал интерфейс, а не просто проверяли, что я сказал, что класс реализует интерфейс. Я прочитал вики zope пару раз, но все еще не вижу гораздо большего использования интерфейсов, чем то, что я сейчас делаю. Итак, мой вопрос в том, для чего еще вы можете использовать эти интерфейсы, и как вы можете использовать их для большего.
4 ответа
Вы можете на самом деле проверить, реализует ли ваш объект или класс ваш интерфейс. Для этого вы можете использовать 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
Интерфейсы также могут быть использованы для установки и тестирования инвариантов. Вы можете найти больше информации здесь:
Там, где я работаю, мы используем интерфейсы, чтобы мы могли использовать ZCA или Zope Component Architecture, которая представляет собой целостную структуру для создания компонентов, которые можно заменять и подключать с использованием Interface
s. Мы используем ZCA, так что мы можем справиться со всеми способами индивидуальной настройки для каждого клиента без необходимости разветвлять наше программное обеспечение или иметь все биты для каждого клиента, портящие основное дерево. К сожалению, вики Zope часто бывают довольно неполными. Есть хорошее, но краткое объяснение большинства функций 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
яйцо. Мы также сохраним конфигурацию блога в 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 (под "открытым" я подразумеваю "вы можете добавить больше случаев из любого яйца"), причем более специфические совпадения интерфейса имеют приоритет над менее специфичными совпадениями (
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
тогда и только тогдаmy.blog.xmldump
случается быть установленным, не делаяmy.blog.rss
зависит отmy.blog.xmldump
,Viewlet
они как маленькиеBrowserView
Это означает, что вы можете подписаться на определенное место на странице. Я не могу вспомнить все детали прямо сейчас, но они очень хороши для таких вещей, как плагины, которые вы хотите разместить на боковой панели.Я не могу вспомнить, являются ли они частью базовой Zope или Plone. Я бы рекомендовал не использовать Plone, если проблема, которую вы пытаетесь решить, на самом деле не нуждается в реальной CMS, так как это большая и сложная часть программного обеспечения, и она имеет тенденцию быть довольно медленной.
Вам не обязательно на самом деле нужно
Viewlet
в любом случае, так какBrowserView
s могут вызывать друг друга, используя 'object/@@some_browser_view' в выражении TAL, или используяqueryMultiAdapter( (ISomething, IHttpRequest), name='some_browser_view' )
но они довольно милыемаркер
Interface
s. МаркерInterface
являетсяInterface
это не обеспечивает никаких методов и никаких атрибутов. Вы можете добавить маркерInterface
любой объект во время выполнения, используяISomething.alsoProvidedBy
, Это позволяет вам, например, изменять, какие адаптеры будут использоваться на конкретном объекте, а какиеBrowserView
s будет определено на нем.
Я прошу прощения за то, что я не вдавался в достаточно подробности, чтобы иметь возможность сразу реализовать каждый из этих примеров, но каждый занял примерно пост в блоге.
Интерфейсы 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'
Я никогда не использовал интерфейсы Zope, но вы можете подумать о написании метакласса, который при инициализации проверяет членов класса на соответствие интерфейсу и вызывает исключение времени выполнения, если метод не реализован.
С Python у вас нет других вариантов. Либо выполните шаг "компиляции", который проверяет ваш код, либо динамически проверяйте его во время выполнения.