tipfy (специфичная микро-среда App-Engine) загружается медленно, но только для определенных «событий», которые являются веб-запросами, которые обслуживает ваш код.В других веб-фреймворках он тоже есть, но tipfy небольшой и достаточно простой, чтобы легко изучать и имитировать его источники для этой цели.
Итак, если вы не можете найти более насыщенную фреймворк событий, который вам точно по вкусу из-запроблема «отложенной загрузки», вы можете выбрать ту, которая требует регистрации / подписки вызываемых объектов, и позволяет регистрировать функции именования strings , как это делает tipfy.Названная таким образом функция, разумеется, будет загружена как раз вовремя, если потребуется, чтобы обслужить какое-то событие.
Позвольте мне привести пример с некоторым упрощенным гипотетическим кодом.Скажем, у вас есть структура событий, которая включает в себя что-то вроде:
import collections
servers = collections.defaultdict(list)
def register(eventname, callable):
servers[eventname].append(callable)
def raise(eventname, *a, **k):
for s in servers.get(eventname, ()):
s(*a, **k)
Внутренние элементы любой реальной структуры событий, конечно, будут богаче, но что-то вроде этого будет заметно на самых низких уровнях..
Итак, для этого требуется, чтобы вызываемый объект загружался во время регистрации ... и, тем не менее, даже не касаясь внутренних компонентов вашей платформы, вы можете легко его расширить.Рассмотрим:
import sys
class LazyCall(object):
def __init__(self, name):
self.name = name
self.f = None
def __call__(self, *a, **k):
if self.f is None:
modname, funname = self.name.rsplit('.', 1)
if modname not in sys.modules:
__import__(modname)
self.f = getattr(sys.modules[modname], funname)
self.f(*a, **k)
Конечно, вам понадобится лучшая обработка ошибок & c, но в этом суть: обернуть строку с именем функции (например, 'package.module.func'
) в объект-обертку, который знает каклениво загрузить его.Теперь register(LazyCall('package.module.func'))
зарегистрирует в нетронутом фреймворке такую обертку - и отложит ее загрузку по запросу.
Этот вариант использования, кстати, мог бы быть использован как достаточно хороший пример идиома Pythonчто некоторые непристойные дураки громко и резко заявляют, что не существуют или не должны существовать, или что-то еще: объект, динамически изменяющий свой собственный класс.Варианты использования для этой идиомы - «обрезать посредника» для объектов, которые существуют в одном из двух состояний, причем переход от первого ко второму необратим.Здесь первое состояние ленивого абонента - «Я знаю имя функции, но у меня нет объекта», второе - «Я знаю объект функции».Поскольку переход от первого ко второму необратим, вы можете каждый раз сокращать небольшие издержки тестирования (или косвенные издержки шаблона проектирования Strategy
), если хотите:
class _JustCallIt(object):
def __call__(self, *a, **k):
self.f(*a, **k)
class LazyCall(object):
def __init__(self, name):
self.name = name
self.f = None
def __call__(self, *a, **k):
modname, funname = self.name.rsplit('.', 1)
if modname not in sys.modules:
__import__(modname)
self.f = getattr(sys.modules[modname], funname)
self.__class__ = _JustCallIt
self.f(*a, **k)
Коэффициент усиленияздесь скромно, так как он в основном просто отсекает одну if self.f is None:
проверку от каждого вызова;но это реальная выгода, без реальных минусов, за исключением того, что заставляет ранее названных непристойных дураков переходить в их типичное злое и бессмысленное безумие (если вы считаете , что как недостаток).выбор реализации зависит от вас, а не от меня - или, к счастью, от них; -).
Как один из вариантов дизайна: устанавливать ли сам патч register
для непосредственного принятия строковых аргументов (иоберните их по мере необходимости), в основном, как это делает tipfy
, или перейдите к явному переносу на сайте регистрации, оставив register
(или subscribe
или как его еще называют) нетронутым.Я не придаю большого значения мантре «явный лучше, чем неявный» в данном конкретном случае, поскольку что-то вроде
register(somevent, 'package.module.function')
столь же явно, как
register(somevent, LazyCall('package.module.function'))
, то естьЭто совершенно ясно, что происходит, и это, возможно, чище / более читабельным.
Тем не менее, действительно приятно, что подход явного переноса оставляет базовый фреймворк нетронутым: куда бы вы ни передавали функцию, теперь вы можете передать имя этогофункция (как строка именования пакетов, модуль и сама функция), без проблем.Итак, если бы я модернизировал существующие фреймворки, я бы пошел на явный подход.
Наконец, если вы хотите зарегистрировать вызываемые объекты, которые являются не функциями, а (например) экземплярами определенных классов или связанными методами таких экземпляров, вы можете добавить LazyCall
в такие варианты, как LazyInstantiateAndCall
& c для этой цели. Конечно, архитектура становится немного более сложной (поскольку вам нужны способы создания экземпляров новых объектов, например, 1054 * и способов идентификации уже существующих), но путем передачи этой работы хорошо спроектированной системе фабрик, это не должно быть слишком плохо. Однако я не буду углубляться в такие уточнения, поскольку этот ответ уже довольно длинный, и в любом случае во многих случаях достаточно простого подхода «назови функцию»! -)