Основы использования словарей в Zope3
Словарь - это объект специального вида, используемый в zope, в основном, в полях схем, как источник пар ключ-значения в полях вида Choice, Select и аналогичных. При этом словарь может использоваться самостоятельно (т.е. определятся где-то в коде и использоватся там же, без выноса на уровень компонентной архитектуры Zope3), так и в виде специальной утилиты, регистрируемой в реестре и доступной для использования на компонентном уровне.
Определение и состав:
Существует классическое определение словаря:
- Словарь
- это множество уникальных ключей, каждому из которых ставится в соответствие значение.
При работе со словарём, по любому данному ключу можно всегда получить связанное с этим ключом значение, либо убедиться, что данного ключа в словаре нет.
Поскольку использование и способ соотнесения ключа и его значения могут различатся (ключ и значение могу совпадать, могут быть различных типов) в среде zope3, для удобства разработчиков, используется другое определение словаря:
- Словарь
- это объект, представляющий собой коллекцию уникальных термов;
- Терм
- некоторая константа или переменная.
Понятие уникальности терма сильно зависит от типа терма, которых в Zope3 может быть три:
- Простой терм
- терм, имеющий только значение (value), которое должно
быть уникальным в пределах словаря. В действительности, данный терм
имеет и ключи, но эти ключи в точности соответствуют значениям,
приведённым к строковому типу.
Пример простого терма: {
1
:1,2
:2,3
:3} - Терм с лексемой
- терм, который, помимо значения, содержит еще и
некоторую лексему (token), или, как её ещё называют, токен. Под токеном
понимается строка без управляющих символов, определяющая
идентификатор данного терма. В пределах словаря токен должен
быть уникальным, так как наличие хотя бы двух одинаковых токенов
нарушит требование взаимной однозначности соответствия "токен-значение".
Пример терма с токеном: {1:
red
, 2:green
, 3:blue
} - Терм с названием
- терм, который помимо токена и значения содержит ещё и некоторое название, которое представляет собой удобную для прочтения человеком строку, которая не обязательно должна быть уникальна в пределах словаря, и которая характеризует собой пару токен-значение. Данная строка очень полезна в том случае, если словарь хранит группы значений, которые можно объединить по некоторому признаку. Для предыдущего примера этим признаком может быть, например строка "colors".
Данное определение не противоречит общеринятому, а лишь адаптирует его к использованию в рамках Zope3.
Интерфейсы, предоставляемые словарями в Zope3 :
Использование словарей на уровне компонентной модели требует, чтобы словарь предоставлял реализацию специальных интерфейсов. В Zope3 наиболее часто используются следующие стандартные интерфейсы:
- ITerm
- объект, представляющий собой простое значение (value) в словаре. Данный интерфейс имеет единственный атрибут value.
- ITokenizedTerm
- наследник интерфейса ITerm, это интерфейс терма словаря, содержащего атрибуты value (унаследованный от ITerm) и token (лексема, представляющая собой 7-битную строку, не содержащую управляющих символов).
- ITitledTokenizedTerm
- наследник интерфейса ITokenizedTerm, интерфейс терма словаря, состоящего из токена, значения (унаследованы) и лексемы (title).
- ISource
- интерфейс набора значений. Данные значения используются в качестве элементов, из которых следует выбрать нужные, в полях типа choise. Набор значений может быть большим, и даже бесконечным. Данный интерфейс имеет метод __contains__(value), который позволяет проверять вхождение элемента с указанным значением (value) в словарь.
- IBaseVocabulary
- интерфейс словаря, наследник интерфейса ISource. Это наиболее общий и базовый интерфейс словаря, в котором описан единственный метод getTerm(value), позволяющий получить некоторый терм словаря по известному значению value. Если происходит попытка получить несуществующий терм, возникает LookupError.
- IIterableVocabulary
- итерируемый словарь, который поддерживает
итерации по разрешённым значениям. Предположительно прекратит своё
существование в Zope 3.3, но в настоящий момент является используемым.
Интерфейс декларирует 2 метода:
- __iter__
- позволяет получить значение итератора по терму;
- __len__
- возвращает количество термов словаря, либо значение sys.maxint;
- IVocabulary
- интерфейс итерируемого словаря. Является наследником интерфейсов IIterableVocabulary и IBaseVocabulary.
- IVocabularyTokenized
- интерфейс словаря, поддерживающего хранение элементов с лексемами (токенами). Метод getTermByToken позволяет получить терм по известной лексеме. Является наследником интерфейса IVocabulary.
Примеры использования словаря:
В качестве примера использования словаря на уровне компонентной модели приведём программный код, получающий интерфейс по его полному точечному имени. Воспользуемся для иллюстрации отладчиком Zope, запустите bin/debugzope и введите:
from zope.schema.interfaces import IVocabularyFactory from zope.app.zapi import getUtility util = getUtility(IVocabularyFactory, name='Interfaces', context=root) # создаем словарь vocabulary = util(root) # получаем терм по лексеме - # полному точечному имени интерфейса token = vocabulary.getTermByToken('zope.interface.Interface') # получаем и возвращаем описание интерфейса print token.token # получаем сам интерфейс print token.value
Здесь использовался стандартный словарь Interfaces, который был получен из стандартного реестра утилит. Реестр содержит множество полезных словарей, полный список которых можно увидеть примерно тут:
http://localhost80/++apidoc++/Utility/%40%40menu.html
Упомянутая в примере переменная root - это контекст вызова, в отладчике используется корневой объект ZODB, внутри вашего кода - обычно перзистент объект, в рамках которого вы вызываете данный код (self)
Работать со словарями ниже уровня компонентной модели несравненно проще, главное, чтобы было чем их заполнить:
from zope.schema.vocabulary import SimpleVocabulary # Создадим словарь vocabulary = SimpleVocabulary.fromValues([1, 2, 3]) # Распечатаем список значений print list(vocabulary) # Получим по ключу терм term= vocabulary.getTerm(1) # Распечатаем токен print term.token # Распечатаем значение print term.value
Как видим, это совсем обычная сущность, просто с некоторыми особенностями.
Использование словарей в полях схем :
В отличие от примеров предыдущего параграфа, в zope3 наиболее распространено использование словарей в полях схем. Для наглядности, приведём два примера подобного использования словарей.
Пример: Использование словаря с известными значениями
Один из наиболее простых способов - задание экземпляра словаря с заранее известными значениями. Примером подобного словаря может быть любой заведомо известный и неизменяемый набор данных, например набор цветов светофора.
Интерфейс с полем myChoice, которое является словарём из заданных значений, определяется следующим образом:
from zope.schema.vocabulary import SimpleVocabulary from zope.interface import Interface from zope.schema import Choice class IMyInterface(Interface): myChoice = Choice(title=u'My Choice', vocabulary = SimpleVocabluary.fromValues([1, 2, 3]) )
Здесь, значением свойства myChoice будет значение словаря, которое выберет пользователь (а не весь словарь сразу).
Пример: Использование словаря на уровне компонентной модели
Недостатком задания словаря в коде является невозможность (или затруденность) его многократного использования или декомпозиции кода на независимые компоненты. Естественное решение в рамках Zope3 - перейти на уровень компонент.
В этом случе использование словаря будет включать в себя три независимых действия:
- Выбрать (придумать) имя для утилиты с интерфейсом IVocabularyFactory (фабрика словаря);
- Создать класс (либо метод) фабрики словаря и зарегистрировать фабрику с выбранным именем;
- Присвоить выбранное имя атрибуту vocabulary поля, использующего словарь.
Еще раз отметим, в этом случае словарь и использующий его класс являются полностью независимыми и взаимозаменяемыми компонентами. А теперь код.
Создание словаря в файле myvocabulary.py:
from zope.schema.vocabulary import SimpleVocabulary def myValuesVocabulary(context): return SimpleVocabulary.fromValues([1, 2, 3])
Регистрация созданного словаря в фале configure.zcml (для этого указывается интерфейс возвращаемого фабрикой объекта, собственно, фабрика и имя словаря):
<configure xmlns="http://namespaces.zope.org/zope"> <utility provides="zope.schema.interfaces.IVocabularyFactory" component=".myvocabulary.myValuesVocabulary" name="My Values" /> </configure>
Использование созданного словаря в файле interfaces.py:
from zope.interface import Interface from zope.schema import Choice class IMyInterface(Interface): myChoice = Choice(title=u'My Choice', vocabulary = 'My Values' )
Приведенному примеру стоит следовать, если вы хотите использовать словарь как независимую компоненту.
Примечание:
При создании словаря в фабрику будет передано значение объекта, в контексте которого создается словарь, что позволяет использовать контекстно-зависимые словари. При этом надо учесть, что если для форм редактирования контекстом является редактируемый объект, то для форм добавления - объект zope.app.container.interfaces.IAdding, контекстом которого, в свою очередь, является редактируемый объект.
Создание словарей:
Уже отмечалось, что в реестре утилит Zope3 зарегистрировано множество полезных словарей. Тем не менее, необходимость в создании собственного словаря возникает настолько часто, что существует несколько готовых инструментов для решения такой задачи. Рассмотрим три наиболее популярные из них.
Создание словаря из комплектующих :
В этом случае словарь объявляется непосредственно в исходном коде, причём к требуемому результату в этом случае можно прийти тремя различными путями, а именно:
- В классе терма реализовать интерфейс ITitledTokenizedTerm, либо воспользоваться готовым zope.schema.vocabulary.SimpleTerm (со свойствами value, token и title);
- В классе словаря реализовать интерфейс IVocabularyTokenized, либо воспользоваться готовым zope.schema.vocabulary.SimpleVocabulary с нужными свойствами и методами;
- Использовать класс словаря напрямую, либо реализовать фабрику словарей, зарегистрировав ее с определенным именем и интерфейсом (zope.schema.interfaces.IVocabularyFactory).
Создание словаря с использованием готовых решений:
Данный способ создания словаря сводится к созданию (или регистрации) объекта, с помощью уже существющих, специально предназначеных для этого zope3 классов. В этом случае задача сводится к выбору того, каким способом создать словарь будет наиболее удобно в данном конкретном случае. При этом для создания словаря рекомендуется пользоваться одним из следующих методов класса zope.schema.vocabulary.SimpleVocabulary:
- fromItems
- позволяет создать словарь из списка пар (лексема, значение). В качестве названия (title) в этом случае будут использованы лексемы;
- fromValues
- позволяет создать словарь из списка значений. В качестве названий и лексем будут использованы значения;
- __init__
- стандартный конструктор. Принимает список уже сформированных термов и на основе этого списка создает словарь.
Создание словаря с помощью SqlVocabulary:
SqlVocabulary - фабрика словарей, которые заполняются данными, полученными с помощью sql-запроса. Данный компонент является наследником zope.app.sqlscript.SQLScript. Запрос, результаты которого используются для создания словаря, должен возвращать ровно 2 колонки: значение (в данном случае оно равно лексеме) и название. Фабрику, выполяющую необходимые действия, необходимо регистрировать с нужным именем и интерфейсом zope.schema.interfaces.IVocabularyFactory.
Примечание:
Продукт SqlVocabulary планируется включить в sqltools
Создание словаря с помощью СontentClass2Resource:
СontentClass2Resource - расширяемое хранилище описаний контент-классов. По содержимому поддерживает набор зарегистрированных фабрик словарей по каждому атрибуту описания контент-класса contentclass2resource.interfaces.IContentClassResource. Имена зарегистрированных фабрик выбираются с учетом выбранного префикса, например rescon_call.
Примечание:
Продукт СontentClass2Resource планируется опубликовать: contentclass2resourse
Базовые классы для создания словарей и работы с их элементами:
В стандартной поставке zope3 реализовано значительное количество классов для работы со словарями. Основными из них являются следующие:
- zope.schema.vocabulary.SimpleTerm
- базовый терм, реализует zope.schema.interfaces.ITitledTokenizedTerm;
- zope.schema.vocabulary.SimpleVocabulary
- базовый словарь, реализует zope.schema.interfaces.IVocabularyTokenized;
- zope.app.component.vocabulary.UtilityTerm
- терм, представляющий собой утилиту, реализует zope.schema.interfaces.ITokenizedTerm;
- zope.app.component.vocabulary.UtilityNameTerm
- терм, представляющий собой имя утилиты, реализует zope.schema.interfaces.ITokenizedTerm;
- zope.app.component.vocabulary.UtilityVocabulary
- словарь, представляющий собой набор утилит, реализует zope.schema.interfaces.IVocabularyTokenized.
Как можно видеть, указанный перечень классов покрывает большую часть интерфейсов, о которых шла речь в предыдущих раделах. Использование указанных классов позволяет реализовать практически любую задачу, посвящённую словарям и работе с ними.
Решения на основе словарей:
Для разработчикое, которые в своей практической деятельности не сталкивались со словарями, этот тип данных может показаться достаточно экзотическим и малопригодным, однако, подобное впечателние является неправильным. В подтверждение, приведём примеры кода по формированию и использованию словарей, которые взяты из реальных задач программирования. Каждый пример, как того требует zope3, состоит из двух файлов: файла с кодом на языке Python и файла с zcml-директивами.
Формирование словаря индексов:
В данном примере термы словаря получаются в результате запроса к каталогу zope.app.catalog.interfaces.ICatalog, который позволяет индексировать объекты, расположенные в ZODB и должен быть уже знаком zope- и plone-разработчикам. Для получения объекта, соответствующего индексу, при формировании каждого отдельно взятого терма словаря, используется генератор уникальных идентификаторов (утилита, реализующая интерфейс IIntIds).
Содержимое файла newsvocabulary.py:
from zope.app.zapi import getUtility from zope.app.catalog.interfaces import ICatalog from zope.app.intid.interfaces IIntIds from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary def newsVocabulary(context): catalog = getUtility(context = context, interface = ICatalog) ids = getUtility(context = context, interface = IIntIds) news = [(i, ids.getObject(i)) for i in catalog.apply(someIndex={published=True})] return SimpleVocabulary([SimpleTerm(title=i.title, value = i, token=id) for (id, i) in news])
Содержимое файла configure.zcml:
<configure xmlns="http://namespaces.zope.org/zope"> <utility provides="zope.schema.interfaces.IVocabularyFactory" component=".newsvocabulary.newsVocabulary" name="PublishedNews" /> </configure>
Пример является достаточно наглядным и простым. В файле newsvocabulary.py происходит импортирование всех нужных классов и реализована фабрика newsVocabulary. Внутри фабрики происходит получение утилит с интерфейсами ICatalog (это, собственно, каталог) и IIntIds (генератор уникальных идентификаторов), после чего происходит выборка проиндексированых каталогом объектов в опубликованном состоянии и получение самих объектов. Последняя строка фабрики, собственно, формирует словарь из списка объектов и их индексов. Файл configure.zcml содержит код регистрации утилиты с указанным именем и предоставляемыми интерфейсами.
Выбор автора из существующего списка пользователей:
В данном примере описано создание словаря, содержащего информацию обо всех пользователях системы. Впоследствии данный словарь можно будет использовать в некоторой схеме для указания, например, автора того или иного документа из числа зарегистрированных пользователей сайта.
Содержимое файла authorsvocabulary.py:
from zope.app.zapi import getUtility from zope.app.security.interfaces import IAuthentication from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary def authorsVocabulary(context): auth = getUtility(context = context, interface = IAuthentication) plugins = auth.getAuthenticatorPlugins() principals = [] for i in plugins: principals.append( [i.principalInfo(k) for k in i.search({'search': ''}] ) return SimpleVocabulary( [SimpleTerm(title=i.title, value = i, token=i.id) for i in principals] )
Содержимое файла configure.zcml:
<configure xmlns="http://namespaces.zope.org/zope"> <utility provides="zope.schema.interfaces.IVocabularyFactory" component=".newsvocabulary.newsVocabulary" name="Authors" /> </configure>
Как видно, единственным принципиальным отличием данного примера от предыдущего является более сложное формирование списка (principals), для его дальнейшего преобразования в словарь.
Заключение:
В настоящем документе были затронуты лишь основные моменты работы со словарями, что позволит при необходимости либо использовать имеющиеся словари, либо конструировать собственные. Приведённые примеры показывают, что работа со словарями является весьма простой и однообразной, а вся разница при работе с базовыми вариантами словарей сводится к разнице способов получения значений для словаря.
Очевидной проблемой при использовании словарей является отсутствие возможности динамического изменения его содержимого по запросу. Примером такой задачи может быть постоянное обновление данных об авторах в примере с выбором автора из существующего списка пользователей, которое не реализуемо стандартными средствами по работе со словарями.
Рекомендуемые источники дополнительной информации по словарям:
http://wiki.zope.org/zope3/VocabularyFields
http://wiki.zope.org/zope3/VocabularyRegistry
zope/schema/vocabulary.py