Траверсеры.txt
2008-01-11 19:00...
Траверсеры:
Как рассказывалось в Сценарий обслуживания запроса.txt, траверсер - это вынесенный в отдельный класс алгоритм поиска и активации объекта в базе данных. Траверсер может делать массу неожиданных вещей:
- Изменять среду выполнения запроса или сам запрос, см. например SiteManager.txt;
- Вернуть вместо одного объекта другой, созданный на его основе;
- Применить особенный алгоритм для поиска объекта по идентификатору;
- Запросить объект из внешнего хранилища.
В качестве примера, сделаем нечеткий траверсер. Суть его в том, чтобы по указанному в URL идентификатору объекта находить наиболее похожий на него из числа идентификаторов, имеющихся в наличии. Рассмотрим пример fuzzytraverser.

Любой траверсер - это вид (адаптер) преобразующий некоторый интерфейс к следующему интерфейсу:
zope.publisher.interfaces.browser.IBrowserPublisher.
Для того, чтобы траверсер вызывался при прохождении некоторого объекта, этот объект должен предоставлять указанный интерфейс. Cоздадим интерфейс IFuzzyFolder для такой цели (см. файл interfaces.py):
from zope.app.container.interfaces import ISimpleReadContainer
class IFuzzyFolder(ISimpleReadContainer):
"""Marker for folders whose contained items keys are fuzzy."""
теперь зарегистрируем его::
теперь зарегистрируем траверсер:
<view
for=".interfaces.IFuzzyFolder"
type="zope.publisher.interfaces.browser.IBrowserRequest"
factory=".fuzzytraverser.FuzzyTraverser"
provides="zope.publisher.interfaces.browser.IBrowserPublisher"
permission="zope.Public"
/>
А теперь посмотрим, как он устроен:
from zope.publisher.interfaces import NotFound
from zope.app import zapi
from zope.app.container.traversal import ContainerTraverser
import difflib
from interfaces import IFuzzyFolder
class FuzzyTraverser(ContainerTraverser) :
__used_for__ = IFuzzyFolder
def publishTraverse(self, request, name):
"""See zope.publisher.interfaces.browser.IBrowserPublisher"""
subob = self.get(name)
if subob is not None:
return subob
return super(FuzzyTraverser, self).publishTraverse(request, name)
def get(self, name) :
res = self.context.get(name, None)
if res is not None :
return res
imax = 0.5
nmax = None
for key in self.context.keys() :
i = difflib.SequenceMatcher(None,name,key).ratio()
if i >= imax :
imax = i
nmax = key
return self.context.get(nmax,None)
Библиотека difflib используется, чтобы найти расстояния (в терминах "длины патча" или дистанции Левенштейна) между предъявленным и имеющимся идентификатором. Если похожего (с дистанцией меньше 0.5) не было найдено - то, значит такого идентификатора нет. Как показывает практика, такой простой алгоритм легко решает проблему легких опечаток в написании URL-а.
Посмотрим, как включается использование такого траверсера для обычной папки (Folder). В fuzzytraverser.demo.configure указываем:
<configure
xmlns:browser="http://namespaces.zope.org/browser"
xmlns="http://namespaces.zope.org/zope">
<class
class="zope.app.folder.folder.Folder">
<implements interface="..interfaces.IFuzzyFolder"/>
</class>
</configure>
Теперь любая папка (Folder) будет обрабатываться нашим траверсером и будет не чувствительна к ошибкам в наборе URL.
Тут есть несколько тонкостей, непреднамеренно опущенных. Первая состоит в том, для любого контейнера уже есть зарегистрированный траверсер, который делает, скорее всего, очень важную работу. Поэтому ваш траверсер должен обязательно порождаться от него, передавая ему управление, так, чтобы он мог выполнить свои функции.
Другая тонкость вытекает из первой: интерфейс, назначаемый объекту, чтобы он траверсился новым траверсером, должен наследоваться от стандартного интерфейса, иначае выбор между двумя траверсерами - новым и стандартным - может превратится в лотерею.
Чтобы оценить количество работы, требуемое для превращение учебного примера в рабочий продукт, можно взглянуть на продукт ng.fuzzytraverser, доступный на PyPI: и строк стало больше, и необходимыми дополнениями обросло.



