Pull to refresh

Ленивая загрузка webapp

Reading time3 min
Views3.1K
В стандартном фреймворке webapp есть много полезного и мало лишнего. Но есть в этом неплохом творении гугла одна особенность — все используемые модули подгружаются при старте приложения. Это удобно для разработки и отладки, но грозит одной проблемой: слишком долгий старт инстанса немаленького приложения. В результате пользователь вынужден ждать пока загрузится всё приложение, даже если для отображения нужной ему страницы нужна лишь малая часть кода.

Сегодня мы «научим» webapp не загружать лишнее.
Решение задачи довольно простое, поэтому сначала будет код, и только потом описание и разъяснения. Код главного модуля приложения с ленивой загрузкой выглядит примерно так:
from google.appengine.ext.webapp import RequestHandler, WSGIApplication, util
import os, sys

dev_server = os.environ.get('SERVER_SOFTWARE').lower().startswith('dev')

class lazy_loader(object):
  def __init__(self, fullname):
    self.modulename, sep, self.objname = fullname.rpartition('.')
    if dev_server: self()

  def __call__(self):
    try: return self.obj()
    except AttributeError:
      if not sys.modules.has_key(self.modulename):
        __import__(self.modulename, globals(), locals(), [], 0)
      self.obj = sys.modules.get(self.modulename).__dict__.get(self.objname)
    return self.obj()

application = WSGIApplication([(route[0], lazy_loader(route[1])) for route in [
  (r'/', 'handlers.MainPage'),
  (r'/search/', 'handlers.Search'),
  (r'/_ah/queue/sendmail', 'mail.SendMail'),
  (r'/_ah/queue/HeavyTask', 'tasks.HeavyTask'),
  ]], debug=os.environ.get('CURRENT_VERSION_ID').lower().startswith('dev'))

def main(): util.run_wsgi_app(application)
if __name__ == '__main__': main()


Lazy loader.


lazy_loader — главное в этом примере. Это маленький объект-прослойка, основная задача которого — максимально быстрая инициализация. За основу был взят пример отсюда, после чего код был немного доработан.

Собственно, при инициализации этот объект только сохраняет в себя имя класса-обработчика запросов. Строка if dev_server: self.__call__() лишь облегчает отладку на сервере разработки и без нее вполне можно обойтись. Определение сервера разработки вынесено за пределы функции для увеличения производительности.

При вызове обработчика lazy_loader сразу передает вызов. Конструкция с отловом AttributeError выбрана потому, что объект self.obj в большинстве случаев должен присутствовать (если имя объекта указано правильно, то отсутствовать он будет только при первом вызове). Если вместо этого проверять self.obj при каждом вызове, то приложение будет выполнять лишние операции; немного, но все же… За «потерянные» AttributeError (если такие вдруг окажутся в коде обработчика) можно не переживать — они все равно всплывут при втором вызове обработчика.

Загрузка обработчика.


Самое, пожалуй, интересное происходит при первом вызове обработчика: обращение к отсутствующему self.obj вызывает AttributeError, во время обработки которого и происходит импорт модуля и получение обработчика. Стандартные выражения питона from package import module могут лишь импортировать модули, чьи имена известны на этапе разработки, поэтому здесь не обойтись без мощной встроенной функции __import__. Использовать ее будем самым простым способом — сперва импортируем модуль, а потом получим его из словаря sys.modules.

Так как каждый импортированный модуль добавляется в sys.modules, есть возможность проверить, не импортирован ли уже необходимый модуль. В данном примере модуль handlers будет импортирован лишь один раз за время жизни инстанса.

Конфигурация webapp.


Для того, чтобы конфигурация маршрутов была более наглядной и менее громоздкой, используется фильтрование списков, которое добавляет lazy_loader во все элементы списка (например, (r'/', 'handlers.MainPage') заменится на (r'/', lazy_loader('handlers.MainPage')).

И ещё одна удобная мелочь, которую я решил не удалять из данного примера — конфигурация отладки webapp в зависимости от версии приложения, указанной в app.yaml. Отладочная информация будет выдаваться, только если версия приложения начинается с «dev» (например, develop, dev-0-1-123, dev-B1-37-2 и т.д.)
Tags:
Hubs:
Total votes 17: ↑16 and ↓1+15
Comments17

Articles