Архив рубрики: Python

WSGI — приложения

WSGI — приложения

Вот пример простейшего WSGI приложения:

def MyApplication(environ, start_response):
     start_response('200 OK', [('Content-type', 'text/plain')])
     yield «Hello, world!»

Помимо yield можно делать return(это основной способ) и print(если позволяет веб-сервер). Обратите внимание, что Apache mod_wsgi запрещает использовать поток вывода, поэтому любая попытка сделать print будет приводить к ошибке.

Давайте теперь разберём по порядку.

Первое, что делает наше приложение, это start_response — эта функция сообщает веб-серверу статус и http-заголовки. Загоовки передаются в виде массива, каждый элемент которого представляет из себя tuple с двумя полями. Первое поле — название заголовка, второе — значение. Соответсвенно, так же передаются и куки.

Затем, мы возвращает результат веб-серверу. Делается это с помощью yield или return. Главное, чтобы возвращаемый объект был итератором. Т.е. вы можете сами определить возвращаемый объект, но как правило используются простые массивы.

Как сделать выдачу в реальном времени

Допустим, на вашем сайте есть такие страницы, расчёт выдачи которых занимает много времени, но вы не хотите заставлять пользователя смотреть в пустую страницу, пока идёт обработка запроса. Для сглаживания таких моментов можно применить следующий способ:

  • Определяете свой собственный класс, делаете этот класс итератором, т.е. определяете методы __iter__() и next()
  • Метод next() переопределяете таким образом, что в нём будет происходить вся обработка запроса. Результаты обработки возвращаются с помощью yield.

Допустим, вы хотите вернуть шапку вашего сайта, тогда вы делаете yield «html-текст вашей шапки» а затем вы обрабатываете запрос дальше и следующий ответ снова выводите через yield.

Пример

Вот простой пример, как сделать выдачу в реальном времени.

class Response()
    def __iter__(self):
        return self
    def next(self):
        «тут начинаем обработку запроса»
        «когда нам уже есть что показать, отдаём ответ»
        yield «Hello»
        «Продолжаем обрабатывать запрос»
        «Допустим, делаем какие-то ресурсоёмкие операции»
        «Когда нам снова есть что отдавать, отдаём»
        yield «World»
        «Ну, теперь мы можем выводит остальную часть страницы»
        yield «!!!»
        «Чтобы остановить итерацию, надо вызвать исключение»
        raise StopIteration

Само WSGI-приложение теперь будет выглядеть вот так:

def Application(environ,start_response):
    answer=Response()
    start_response('200 OK',[(«Content-type»,»text/html»)])
    return answer

Автор: D1VER
Дата публикации: 2013-08-18T09:32:00.000-07:00

Перевод документации Sphinx. Разметка уровня параграфа + Вставка кода

Автор: Ishayahu Lastov

Запуск процессов в tulip

В последнее время я работаю над запуском процессов в tulip: PEP 3156 и реализация на гуглокоде

Состояние дел на сегодня

Базовые конструкции выглядят так:

Транспорт:

class BaseTransport:
"""Base ABC for transports."""

def get_extra_info(self, name, default=None):
"""Get optional transport information."""

def close(self):
"""Closes the transport.

Buffered data will be flushed asynchronously. No more data
will be received. After all buffered data is flushed, the
protocol's connection_lost() method will (eventually) called
with None as its argument.
"""

class SubprocessTransport(BaseTransport):

def get_pid(self):
"""Get subprocess id."""

def get_returncode(self):
"""Get subprocess returncode.

See also
http://docs.python.org/3/library/subprocess#subprocess.Popen.returncode
"""

def get_pipe_transport(self, fd):
"""Get transport for pipe with number fd."""

def send_signal(self, signal):
"""Send signal to subprocess.

See also:
http://docs.python.org/3/library/subprocess#subprocess.Popen.send_signal
"""

def terminate(self):
"""Stop the subprocess.

Alias for close() method.

On Posix OSs the method sends SIGTERM to the subprocess.
On Windows the Win32 API function TerminateProcess()
is called to stop the subprocess.

See also:
http://docs.python.org/3/library/subprocess#subprocess.Popen.terminate
"""

def kill(self):
"""Kill the subprocess.

On Posix OSs the function sends SIGKILL to the subprocess.
On Windows kill() is an alias for terminate().

See also:
http://docs.python.org/3/library/subprocess#subprocess.Popen.kill
"""

Протокол:

class BaseProtocol:
"""ABC for base protocol class.

Usually user implements protocols that derived from BaseProtocol
like Protocol or ProcessProtocol.

The only case when BaseProtocol should be implemented directly is
write-only transport like write pipe
"""

def connection_made(self, transport):
"""Called when a connection is made.

The argument is the transport representing the pipe connection.
To receive data, wait for data_received() calls.
When the connection is closed, connection_lost() is called.
"""

def connection_lost(self, exc):
"""Called when the connection is lost or closed.

The argument is an exception object or None (the latter
meaning a regular EOF is received or the connection was
aborted or closed).
"""

class SubprocessProtocol(BaseProtocol):
"""ABC representing a protocol for subprocess calls."""

def pipe_data_received(self, fd, data):
"""Called when subprocess write a data into stdout/stderr pipes.

fd is int file dascriptor.
data is bytes object.
"""

def pipe_connection_lost(self, fd, exc):
"""Called when a file descriptor associated with the child process is
closed.

fd is the int file descriptor that was closed.
"""

def process_exited(self):
"""Called when subprocess has exited.
"""

Нужные методы в event loop:

class AbstractEventLoop:
"""Abstract event loop."""

def subprocess_shell(self, protocol_factory, cmd, *, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
**kwargs):
"""Run cmd in shell"""

def subprocess_exec(self, protocol_factory, *args, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
**kwargs):
"""Subprocess *args"""

Т.е. интерфейс запуска процесса почти повторяет subprocess.Popen за исключением того, что subprocess.PIPE теперь вариант по умолчaнию. Заодно еще избавляемся от кошмара с правильным использованием shell=True (см. пост на эту тему). Поддерживаются только байтовые потоки, как и везде в tulip.

Оно уже в целом работает на Unix, код для Windows тоже готовится.

Делает всё что можно и нужно за исключением TTY. C TTY ван Россум предложил пока не связываться, да и subprocess его не поддерживает.

Проблема

Рабочие транспорты и протоколы — это, конечно, классно. Вполне подходящий низкоуровневый строительный блок.

Но простому программисту хочется иметь что-то более удобное и привычное.

Для tulip это должен быть код на основе yield from.

Проблема в том, что для процессов мы имеем не один поток ввода-вывода, а три однонаправленных: stdin, stdout и stderr. А еще процесс может сам решить закрыться, и это тоже нужно удобно обрабатывать.

Просьба

Я пытался придумать что-то такое, но результат пока мне не нравится.

Может, кто сумеет посоветовать дельную конструкцию? Или указать на готовую библиотеку, у которой можно поучиться?

Автор: Andrew Svetlov

Удаление объектов сборщиком мусора

На Хабре появилась толковая статьяо неприятностях, которые может доставить Garbage Collector.

Отмечу актуальность описания проблемы и полную корректность решения для tornado.

Однако не могу не обратить внимание на слабое освещение того, как именно работает сборщик мусора в CPython.

Почему при работающем garbage collector появляется так много неудаляемых объектов?

Немного теории.

Сборщик мусора имеет три поколения (счёт начинается с нуля). При создании объекта он попадает в нулевое поколение.

У каждого поколения есть счётчик и порог. Работает эта пара так:

  • При добавлении объекта в поколение счётчик увеличивается.
  • При выбывании из поколения счётчик уменьшается.
  • Когда счётчик превысит пороговое значение — по всем объектам из поколения пройдётся сборщик мусора. Кого найдёт — удалит.
  • Все выжившие в поколении объекты перемещаются в следующее (из нулевого в первое, из первого во второе). Из второго поколения объекты никуда не попадают и остаются там навечно.
  • Перемещённые в следующее поколение объекты меняют соответствующий счетчик, и операция может повториться уже для следующего поколения.
  • Счётчик текущего поколения сбрасывается.

Объекты, подлежащие уничтожению но имеющие переопределённый метод __del__ не могут быть собраны. Причина проста: эти объекты могут ссылаться друг на друга.

Python не способен определить безопасный порядок вызова __del__. Если вызывать деструкторы в произвольном порядке, то можно получить ситуацию вида:

  • Деструктор объекта a для работы требует объект b.
  • Последний в своём деструкторе обращается к объекту a.
  • Если вызовем __del__ у a, то деструктор b не сможет отработать нормально. Ссылка на a будет иметь значение None.

Чтобы не заставлять программиста корректно разрешать такие ситуации было принято решение не уничтожать подобные объекты а просто перемещать их в gc.garbage — и дальше программист пусть сам разбирается что делать с этим мусором.

К слову, в потоках-демонах тоже возникает ситуация подобная описанной выше, но там программист должен быть готов к тому что переменная внезапно стала None. Подробности смотрите здесь

Перейдём к практической части.

Пример

Для иллюстрации рассмотрим немного изменённый пример из приведенной в самом начале статьи.

Имеем классическую древовидную структуру:

class Node(object):
parent = None

def __init__(self, *children):
self.children = list(children)
for node in self.children:
node.parent = self

@classmethod
def tree(cls, depth=1, numchildren=1):
if depth == 0:
return []
return [cls(*cls.tree(depth-1, numchildren))
for _ in range(numchildren)]

Родитель и потомки напрямую связаны друг с другом циклической связью. Метод tree создает дерево нужной глубины.

Добавляем garbage collection hook для того чтобы увидеть когда срабатывает сборщик мусора и сколько объектов он уничтожает:

import gc

def gc_cb(phase, info):
if not info['collected'] and not info['uncollectable']:
return
print("{0}:t{1[generation]}t{1[collected]}t{1[uncollectable]}".format(
phase, info))

gc.callbacks.append(gc_cb)

Наконец, делаем много-много наших деревьев и смотрим как они разрушаются:

for n in range(20):
for _ in range(n):
Node.tree(depth=5, numchildren=6)

Пороги стоят стандартные:

>>> gc.get_threshold()
(700, 10, 10)

700 объектов в нулевом поколении и по 10 в первом и во втором.

Анализ

Теперь о том, почему образуется столько мусора.

Пример напечатает что-то вроде такого (вырезка из очень длинного результата):

...
stop: 1, 4665, 0
stop: 2, 79305, 0
stop: 1, 4665, 0
stop: 2, 79305, 0
stop: 1, 4665, 0
stop: 1, 4665, 0
stop: 1, 4665, 0
stop: 2, 97965, 0
stop: 1, 4665, 0
stop: 2, 79305, 0
stop: 1, 4665, 0
...

За один вызов Node.tree(depth=5, numchildren=6) создается 9330 тесно связанных объектов, которые нельзя разрушить в 0 поколении (помним, что порог 700). Значит они попадают в первое, а большая часть даже во второе поколение (9330>700*10). Наконец все 9330 объекта созданы, можно разрушать.

На уменьшении счётчиков ссылок на объекты ничего убрать не получится. Поэтому ждём, когда опять превысим порог в 700 (на следующем вызове Node.tree, конечно).

Собираем нулевое поколение (оно оказывается заполнено свежими данными и поживиться почти ничем не удаётся).

А сборщик мусора для поколения 1 вызовется только если туда попадут как минимум 10 объектов из поколения 0.

Хорошо, мы добрались до сбора в 1 поколении. Часть циклов мож
но уничтожить сразу (два поколения для анализа лучше одного), некоторые переправляются в поколение 2. В котором сборщик запускается тоже если в свою очередь превысили порог.

Что случается ещё реже и таким образом наши объекты накапливаются во втором поколении. Когда сборщик мусора доходит до него, то всё чистит.

Проблема в том, что до последнего поколения дело доходит относительно редко.

В результате имеем не слишком типичный для сборщика мусора случай.

Чиним

Конечно, лучше всего не доводить дело до сборщика мусора вообще в случаях подобным нашему синтетическому примеру.

Разрушать ссылки вручную через вызов del или присваивания Noneочень неудобно, но есть и другой способ.

Воспользуемся слабыми ссылками на родителя:

import weakref

class Node(object):
parent = None

def __init__(self, *children):
self.children = list(children)
for node in self.children:
node.parent = weakref.proxy(self)

Я предпочитаю weakref.ref как дающий больший контроль (всегда можно добавить свойство):

class Node(object):
_parent = None

def __init__(self, *children):
self.children = list(children)
for node in self.children:
node._parent = weakref.ref(self)

@property
def parent(self):
if self._parent is None:
return None
else:
return self._parent()

В любом варианте дело до сборщика мусора не дойдёт и объекты будут уничтожены сразу как только перестанут быть нужны.

Если вариант со слабыми ссылками почему-то не проходит можно просто увеличить пороги. У нас создаётся за раз 9330 объектов? Поставим порог для первого поколения в 10000.

gc.set_threshold(10000, 100, 100)

Результат выглядит куда лучше:

...
stop: 0, 4665, 0
stop: 0, 4665, 0
stop: 0, 4665, 0
stop: 0, 4665, 0
stop: 0, 4665, 0
stop: 1, 919005, 0
stop: 0, 4665, 0
stop: 0, 4665, 0
stop: 0, 4665, 0
stop: 0, 4665, 0
...

Как видим сборщик мусора уничтожает почти всё на первом проходе, и лишь иногда требуется второй. Правда, смущает цифра 919005.

Именно потому что не всё прибивается на первом проходе, а второй наступает нескоро.

Уменьшаем второй порог:

gc.set_threshold(10000, 10, 10)

Ага, теперь всё красиво:

...
stop: 1, 83970, 0
stop: 0, 9330, 0
stop: 0, 4665, 0
stop: 0, 4665, 0
stop: 1, 74640, 0
stop: 0, 4665, 0
stop: 0, 4665, 0
stop: 1, 102630, 0
stop: 0, 4665, 0
stop: 0, 4665, 0
...

Выводы

В результате всё просто. Используем слабые ссылки. Если это по каким-то причинам невозможно — поднимаем пороги.

Но при этом нужно помнить, что сборщик мусора будет запускаться реже.

Установка порогов в слишком большое значение способно в нашем случае съесть память не менее успешно, чем если бы эти значения оставались установленными по умолчанию.

Автор: Andrew Svetlov

Начало курсов «Learn Python»

Занятия начинаются в эту субботу, 29 июня 2013.

Записавшимся должно прийти на почту письмо с местом, временем и прочими деталями.

Автор: Andrew Svetlov