Найти - Пользователи
Полная версия: Внедрение mix-in в рантайме
Начало » Python для экспертов » Внедрение mix-in в рантайме
1
Змей
Добрый день.

Есть дерево наследования, обрабатывающее HTTP запросы. И есть острое желание сделать работу “фабрики классов” (если этот термин вообще можно здесь применить) пошаговой, с определением конкретных типов по мере углубления в реальную структуру сайта, примерно так:

class Root(object):
def __init__(self):
self.__status = {200: 'Ok', 404: 'NotFound',}
self.__headers = []


def SetStatus(self, status_code):
self.__headers.append('Status: %d %s' % status_code, self.__status[status_code])


def Reply(self, url):
if self.__BadUrl(url):
self.SetStatus(404)
content = None
else:
if url.startswith('/admin'):
self.__class__ = AdminEngine
else:
self.__class__ = UserEngine

content = self.Reply(url)

r = '\n'.join(self.__headers) + '\n\n'
if content: r += content
return r



class AdminEngine(Root):
def Reply(self, url):
self.SetStatus(200)
if self.LogOnSuccess():
self.__InitDB('adminlogin', 'adminpass')
return self.InnerShrine()
else: return self.LogOn()
И вроде бы все ничего, но не могу решить проблему разнесения дочерних классов по отдельным файлам. Каждый класс наследуется от своего предка и знает о своем потомке.
Выделить фабрику классов в чистом виде? Ей придется слишком много знать о структуре сайта.
Отказаться от функциональности базового класса (пока ее не так уж и много, но разработка в самом разгаре) и унаследовать “детей” от object?
Андрей Светлов
За self.__class__ = AdminEngine вам когда-нибудь дадут по рукам.
Змей
Есть идея, как реализовать функционал корректнее, или тебе так, чисто постебаться?
Змей
Ладно, перефразируем задачу.

Есть класс Engine. Он знает, какие урлы потенциально кошерны, а какие - заведомо нет и должны окончиться 404 ответом. Он знает, как начинается путь к админке, и знает, что остальные пути - для пользователей сайта.

Есть класс UserEngine(Engine). Он знает, что урлы должны начинаться с «http://domain.ru/» и заканчиваться на «/», и при необходимости легко отдаст 403 редирект. И еще он знает, что на /gallery/ висит галерея, на /news/ - новости, а все остальные пути потенциально ведут к некоторым статьям. Также он устанавливает подключение к СУБД с ограниченными (или не очень) правами, в зависимости от контекста.

Есть класс AdminEngine(Engine). Ему пофигу, как начинаются и заканчиваются урлы, до тех пор, пока он может их обработать. И он знает, какой именно потомок должен отработать по каждому разделу админки. А еще он знает, что сперва надо посмотреть на запрос и при необходимости вызвать соответствующий обработчик соответствующего потомка, после чего почистить параметры и дать редирект для избежания проблем с F5.

Есть несколько классов: ArticleEngine(UserEngine), … - они работают каждый по своему разделу, но всем им нужна БД с одинаковыми правами. И есть еще несколько классов: ArticleEditor(AdminEngine), … - аналогично.


На мой взгляд, это иерархия наследования чистой воды. Но вот незадача: приступая к разбору адреса, я НЕ ЗНАЮ, какой конкретно обработчик должен быть создан. На каждом уровне наследования известно только одно: пора ли уже вернуть что-то определенное, или надо спихнуть работу на кого-то из младших. Это позволяет, в частности, легко подключать новые секции без ведома отцов. Вытаскивать все в отдельную фабрику - значит основательно положить на инкапсуляцию, со всеми вытекающими. Не говоря уже о том, КАК эта фабрика будет выглядеть.
Андрей Светлов
self.__class__ = NewClass - никак не определенная операция в стандарте.
Т.е. стандарт ее не разрешает, но и не запрещает явно.
CPython позволяет это делать. С оговорками - некоторые существующие библиотеки очень не любят подобные действия.
Jython, насколько я помню, лоялен. Pypy - ломался.

Ну ладно. Кого волнуют эти экзотические диалекты да сторонние библиотеки. Пример-то работает?
Ну как работает… По крайней мере не успело сломаться. Хотя вы уже ощутили неудобство с взаимозависимостями. Это - плохой запах дурного дизайна.

А как вы собираетесь гарантировать, что новоподставленный класс полностью совместим со старым? Например, AdminEngine.Reply не потребует наличия атрибута, которого в Root просто не было?

Кто будет переключать обратно на Root? А если никто - то отчего бы сразу не создать AdminEngine и дело с концом.

В известных мне системах с path traversal новый resource создается на каждый элемент url path. Или используется текущий на данном шаге. И - никогда не меняется через грязный хак.

В общем, ваша схема может иногда работать, а по мере расширения станет ломаться в странных местах. Вы ее будете чинить, но до конца ликвидировать все проблемы невозможно.
Андрей Светлов
Как по мне - не нужна Engine. А привык думать о таком в терминах traversal.
Посмотрите на картинку из http://docs.pylonsproject.org/projects/pyramid/1.0/narr/traversal.html
Змей
Пример - да, работает, хотя конкретно те строки, которые я привел в качестве примера, написаны от балды. Переключать тип объектов-обработчиков обратно на корень не требуется, ибо все, что от них нужно - конкретный HTTP ответ, потом смерть.

AdminEngine.Reply не потребует наличия атрибута, которого в Root не было, потому что открытых данных-членов в Root (как и в большинстве прочих классов) вообще нет. Все строго через функции доступа. Я так понимаю, вопрос по совместимости возник из моего предположения отказаться от наследования? Иначе смысл вообще не понятен.

Ладно, раз замена __class__ - хак, я согласен с тем, что это плохо и может внезапно отлиться хорошим геморроем. Я думал, раз интерпретатор кушает - значит, все в порядке )) Traversal сейчас почитаю, спасибо.
Андрей Светлов
Да. У вас был Root.Reply, который внезапно стал AdminEngine.Reply. Программиста вроде бы никто не предупреждал, что в AdminEngine можно делать не всё, что было бы доступно при традиционной работе с классами. Например, конструктор никогда не вызывается и т.д. Это обескураживает.
То, что я вам предлагаю - разбить url path на сегменты через ‘/’, а потом подсовывая очередной сегмент ресурсу получать новый ресурс. Которому опять давать следующий сегмент. Почти как у вас, только создавать новый экземпляр в случае нужды вместо мутирования текущего.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB