Использование реестра адаптеров
Данный документ содержит небольшую демонстрацию пакета zope.interface
и его
реестра адаптеров. Документ рассчитывался как конкретный, но более узкий пример
того как использовать интерфейсы и адаптеры вне Zope 3.
Сначала нам необходимо импортировать пакет для работы с интерфейсами:
>>> import zope.interface
Теперь мы разработаем интерфейс для нашего объекта - простого файла. Наш файл будет содержать всего один атрибут - body, в котором фактически будет сохранено содержимое файла:
>>> class IFile(zope.interface.Interface):
...
... body = zope.interface.Attribute(u'Содержимое файла.')
...
Для статистики нам часто необходимо знать размер файла. Но было бы несколько топорно реализовывать определение размера прямо для объекта файла, т.к. размер больше относится к мета-данным. Таким образом мы создаем еще один интерфейс для представления размера какого-либо объекта:
>>> class ISize(zope.interface.Interface):
...
... def getSize():
... 'Return the size of an object.'
...
Теперь мы должны создать класс реализующий наш файл. Необходимо что бы наш
объект хранил информацию о том, что он реализует интерфейс IFile
. Мы также
создаем атрибут с содержимым файла по умолчанию (для упрощения нашего
примера):
>>> class File(object):
...
... zope.interface.implements(IFile)
... body = 'foo bar'
...
Дальше мы создаем адаптер, который будет предоставлять интерфейс ISize
получая любой объект предоставляющий интерфейс IFile
. По соглашению мы
используем атрибут __used_for__
для указания интерфейса который как мы
ожидаем предоставляет адаптируемый объект, IFile
в нашем случае. На самом
деле этот атрибут используется только для документирования. В случае если
адаптер используется для нескольких интерфейсов можно указать их все в виде
кортежа.
Опять же по соглашению конструктор адаптера получает один аргумент - context
(контекст). В нашем случае контекст - это экземпляр IFile
(объект,
предоставляющий IFile
) который используется для получения из него размера.
Так же по соглашению контекст сохраняется а адаптере в атрибуте с именем
context
. Twisted комьюнити ссылается на контекст как на объект original
.
Таким образом можно также дать аргументу любое подходящее имя, например
file
:
>>> class FileSize(object):
...
... zope.interface.implements(ISize)
... __used_for__ = IFile
...
... def __init__(self, context):
... self.context = context
...
... def getSize(self):
... return len(self.context.body)
...
Теперь когда мы написали наш адаптер мы должны зарегистрировать его в реестре адаптеров, что бы его можно было запросить когда он понадобится. Здесь нет какого-либо глобального реестра адаптеров, таким образом мы должны самостоятельно создать для нашего примера реестр:
>>> from zope.interface.adapter import AdapterRegistry
>>> registry = AdapterRegistry()
Реестр содержит отображение того, что адаптер реализует на основе другого
интерфейса который предоставляет объект. Поэтому дальше мы регистрируем адаптер
который адаптирует интерфейс IFile
к интерфейсу ISize
. Первый аргумент к
методу register()
реестра - это список адаптируемых интерфейсов. В нашем
случае мы имеем только один адаптируемый интерфейс - IFile
. Список
интерфейсов имеет смысл для использования концепции мульти-адаптеров, которые
требуют нескольких оригинальных объектов для адаптации к новому интерфейсу. В
этой ситуации конструктор адаптера будет требовать новый аргумент для каждого
оригинального интерфейса.
Второй аргумент метода register()
- это интерфейс который предоставляет
адаптер, в нашем случае ISize
. Третий аргумент - имя адаптера. Сейчас нам не
важно имя адаптера и мы передаем его как пустую строку. Обычно имена полезны
если используются адаптеры для одинакового набора интерфейсов, но в различных
ситуациях. Последний аргумент - это класс адаптера:
>>> registry.register([IFile], ISize, '', FileSize)
Теперь мы можем использовать реестр для запроса адаптера:
>>> registry.lookup1(IFile, ISize, '')
<class '__main__.FileSize'>
Попробуем более практичный пример. Создадим экземпляр File
и создадим адаптер
использующий запрос реестра. Затем мы увидим возвращает ли адаптер корректный
размер при вызове getSize()
:
>>> file = File()
>>> size = registry.lookup1(IFile, ISize, '')(file)
>>> size.getSize()
7
На самом деле это не очень практично, т.к. нам нужно самим передавать все
аргументы методу запроса. Существует некоторый синтаксический леденец который
позволяет нам получить экземпляр адаптера просто вызвав ISize(file)
. Что бы
использовать эту функциональность нам понадобится добавить наш реестр к списку
adapter_hooks, который находится в модуле с адаптерами. Этот список хранит
коллекцию вызываемых объектов которые вызываются автоматически когда вызывается
IFoo(obj); их предназначение - найти адаптеры которые реализуют интерфейс для
определенного экземпляра контекста.
Необходимо реализовать свою собственную функцию для поиска адаптера; данный
пример описывает одну из простейших функций для использования с реестром, но
также можно реализовать поисковые функции которые, например, используют
кэширование, или адаптеры сохраняемые в базе. Функция поиска должна принимать
желаемый на выходе интерфейс (в нашем случае ISize
) как первый аргумент и
контекст для адаптации (file
) как второй. Функция должна вернуть адаптер,
т.е. экземпляр FileSize
:
>>> def hook(provided, object):
... adapter = registry.lookup1(zope.interface.providedBy(object),
... provided, '')
... return adapter(object)
...
Теперь мы просто добавляем нашу функцию к списку adapter_hooks
:
>>> from zope.interface.interface import adapter_hooks
>>> adapter_hooks.append(hook)
Как только функция зарегистрирована мы можем использовать желаемый синтаксис:
>>> size = ISize(file)
>>> size.getSize()
7
После нам нужно прибраться за собой, что бы другие получили чистый список
adaper_hooks
после нас:
>>> adapter_hooks.remove(hook)
Это все. Здесь намеренно отложена дискуссия об именованных и мульти-адаптерах,
т.к. данный текст рассчитан как практическое и простое введение в интерфейсы и
адаптеры Zope 3. Для более подробной информации имеет смысл прочитать
adapter.txt
из пакета zope.interface
, что бы получить более формальное,
справочное и полное трактование пакета. Внимание: многие жаловались, что
adapter.txt
приводит их мозг к расплавленному состоянию!