WWW.DISS.SELUK.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА
(Авторефераты, диссертации, методички, учебные программы, монографии)

 

Pages:     || 2 | 3 |

«Введение в асинхронное программирование и Twisted Dave Peticolas перевод Nina Evseenko верстка Андрей Березовский Содержание 1 С чего мы начнем вначале 6 1.1 Предисловие......................... ...»

-- [ Страница 1 ] --

Введение в асинхронное программирование и Twisted

Dave Peticolas

перевод Nina Evseenko

верстка Андрей Березовский

Содержание

1 С чего мы начнем вначале 6

1.1 Предисловие................................................ 6

1.2 Модели................................................... 6 1.3 Мотивация..... ............................................ 7 1.4 Дальше и больше............................................. 9 2 Медленная поэзия и апокалипсис 2.1 Предположения о навыках........................................ 2.2 Предположения о компьютере..................................... 2.3 Медленная поэзия............................................ 2.4 Блокирующий клиент........................................... 2.5 Асинхронный клиент........................................... 2.6 Пристальный взгляд........................................... 2.7 Упражнения................................................ 3 Наш первый взгляд на Twisted 3.1 Ничего не делать - путь Twisted..................................... 3.2 Привет, Twisted.............................................. 3.3 Кто использует callback’и?........................................ 3.4 До свидания, Twisted........................................... 3.5 Возьми это на себя, Twisted....................................... 3.6 Поэзию, пожалуйста........................................... 3.7 Упражнения................................................ 4 Twisted поэзия 4.1 Наш первый Twisted клиент....................................... 4.2 Twisted интерфейсы........................................... 4.3 Еще про callback’и............................................. 4.4 Резюме................................................... 4.5 Упражнения................................................ 5 Улучшенная Twisted поэзия 5.1 Абстрактный экспрессионизм...................................... 5.2 Без цикла в мозге............................................. 5.3 Транспорты

5.4 Протоколы................................................. 5.5 Протокольные фабрики......................................... 5.6 Получение поэзии 2.0: первая кровь.0................................. 5.7 Упрощение клиента............................................ 5.8 Резюме................................................... 5.9 Упражнения................................................ 6 Дальнейшие улучшения 6.1 Поэзия для всех.............................................. 6.2 Клиент 3.0................................................. 6.3 Обсуждение................................................ 6.4 Когда дела плохи............................................. 6.5 Клиент 3.1................................................. 6.6 Резюме................................................... 6.7 Упражнения................................................ 7 Отложенные вызовы 7.1 Обратные вызовы и их последователи

7.2 Deferred................................................... 7.3 Резюме..... .............................................. 7.4 Упражнения................................................ 8 Отложенная поэзия 8.1 Клиент 4.0................................................. 8.2 Обсуждение................................................ 8.3 Связь между deferred’ами, callback’ми, реактором.......................... 8.4 Резюме................................................... 8.5 Упражнения................................................ 9 Deferred’ы, часть вторая 9.1 Дальнейшие выводы про callback’и................................... 9.2 Прекрасная структура Deferred’ов................................... 9.3 Callback’и и Errback’и, по двое



9.4 Deferred симулятор............................................ 9.5 Резюме..... .............................................. 9.6 Упражнения................................................ 10 Преобразованная поэзия 10.1 Клиент 5.0................................................. 10.2 Клиент 5.1................................................. 10.3 Резюме................................................... 10.4 Упражнения................................................ 11 Ваша поэзия обслуживается 11.1 Twisted поэтический сервер....................................... 11.2 Обсуждение................................................ 11.3 Упражнения................................................ 12 Сервер, преобразующий поэзию 12.1 Еще один сервер............................................. 19.3 Действительно отмененные Deferred’ы................................ 20.7 Упражнения для особо мотивированных............................... 21.5 Упражнения для поразительно мотивированных........................... 1. С чего мы начнем вначале Данная статья является переводом. Оригинал, который можно найти на странице Twisted Introduction, был написан Dave Peticolas.

1.1. Предисловие В рассылке Twisted не так давно появился вопрос: можно ли где-нибудь найти описание, которое позволит быстро овладеть техникой использования Twisted? На данный момент такого описания не существует, и данное руководство им не является.

Если вы новичок в асинхронном программировании, то лучше изучать основные принципы постепенно.

Исходя из продолжительного опыта использования Twisted и познания всех тонкостей и сложностей, данное пособие начинается с общего понятия модели асинхронного программирования. Большая часть кода Twisted понятна и хорошо написана, документация на сайте хорошая, по меньшей мере с точки зрения стандартов свободного ПО. Но без понятия модели, чтение кода Twisted или кода, использующего Twisted, или даже чтение документации, превратится в головную боль.

Таким образом, первые главы помогают осознать модель асинхронного программирования, а последующие - постичь особенности программирования с использованием Twisted. В самом начале мы совсем не будем использовать Twisted. Вместо этого, для иллюстрации функционирования асинхронной системы мы будем использовать простые программы на Python’е. И как только мы начнем использовать Twisted, мы начнем с очень низкого уровня, который вы не использовали бы в повседневном программировании. Twisted высоко абстрактная система, и это дает вам огромное преимущество при решении проблем. Но, когда вы изучаете Twisted, в особенности, когда вы пытаетесь понять как Twisted реально работает, многоуровневые абстракции могут быть препятствием. Поэтому мы начнем с самых основ.

После того, как вы осознаете модель, вам будет проще читать документацию и исходный код Twisted.

Так что - начнем.

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

Заметьте, что термин “задача” используется не в техническом смысле: “нечто, что нужно выполнить”.

Первая модель, которую мы рассмотрим, - однопоточная синхронная модель, изображенная на рисунке 1 ниже:

Это самый простой стиль программирования. Одновременно выполняется только одна задача, следующая задача выполняется только после того, как предыдущая завершилась. И, если задачи всегда выполняются в определенном порядке, то запуск следующей задачи может осуществиться, если все предыдущие задачи завершились без ошибок.

Мы можем сравнить синхронную модель с многопоточной моделью, изображенной на рисунке 2:

В этой модели каждая задача выполняется в отдельном потоке. Потоки управляются операционной системой, и в случае наличия нескольких процессоров и/или ядер, могут выполняться параллельно, или их выполнение может чередоваться в случае одного процессора. Смысл в том, что в тредовой модели деталями управляет операционная система, и программист просто думает в терминах независимых потоков инструкций, которые могут выполняться одновременно. Хотя диаграмма простая, на практике потоковые программы могут быть достаточно сложными, поскольку нужно координировать потоки между собой. Потоковая координация и взаимодействие являются отдельной, достаточно сложной темой.

Некоторые программы реализуют параллельность, используя несколько процессов вместо нескольких потоков. Хотя, с точки зрения реализации есть отличия, для наших целей это одна и та же модель, как на рисунке 2.

Теперь мы можем перейти к асинхронной модели на рисунке 3:

В этой модели задачи выполняются поочередно в одном потоке. Это проще, чем в случае потоков, поскольку программист всегда знает, что когда одна задача выполняется, другая - нет. Хотя в однопроцессорной системе в потоковой программе задачи также будут выполняться поочередно, программист, использующий потоки, все равно должен думать в терминах рисунка 2, а не 3, чтобы программа работала корректно при переходе на многопроцессорную систему. Но однопоточная асинхронная система всегда будет чередовать выполнение задач даже на многопроцессорной системе.

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

Скорее, это контролируется операционной системой, и программист должен предполагать, что поток может приостановиться и смениться другим практически в любой момент времени. Что отличается от задач в асинхронной модели, которые выполняются до того момента, когда явно не передают контроль другим задачам. Это значительно проще, чем в случае потоков.

Заметим, что возможно смешивать асинхронные и потоковые модели, и использовать оба варианта в одной системе. Но, для большей части этого введения, мы будем придерживаться “чистых“ асинхронных систем с управлением в один поток.

1.3. Мотивация Мы увидели, что асинхронная модель проще, чем потоковая, поскольку есть только единственный поток инструкций и задач, который явно передает управление, вместо приостановки в произвольный момент времени. Но асинхронная модель является явно более сложной, чем синхронная.

Программист должен организовать задачу как последовательность маленьких шагов, которые периодически запускаются.

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

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

Так почему же мы бы выбрали асинхронную модель? Существует по меньшей мере две причины. Первая - если одна или более задач отвечают за реализацию интерфейса, то путем чередования задач, система сможет отвечать на пользовательский ввод даже тогда, когда в “фоне” выполняется другая задача.

Так как фоновые задачи не могут выполниться быстрее, система будет более вежливой для того, кто ее использует. Однако, существуют условия, когда асинхронная система будет лучше по сравнению с синхронной, иногда намного, в смысле выполнения всех задач за более короткий промежуток времени. Эти условия связаны с задачами, которые вынуждены ожидать событий или блокироваться, как это изображено на рисунке 4:

На рисунке серые секции представляют собой периоды времени, когда определенная задача ожидает (блокируется) и не выполняется. Почему же задача может быть заблокирована? Частая причина - ожидание выполнения ввода-вывода, связанное с перенесением данных из или во внешнее устройство. Обычный процессор может управлять переносом данных на порядок быстрее, по сравнению с максимальной скоростью переноса данных для диска или сети. Таким образом, синхронная программа, которая выполняет много операций ввода-вывода, будет часто блокироваться в момент обращения к диску или сети. Такая синхронная программа часто называется блокирующаяся программа (blocking program).

Заметьте, что на рисунке 4 изображена блокирующаяся программа, которая немного похожа на асинхронную программу на рисунке 3. Это не совпадение. Основная идея в асинхронной модели - это то, что, когда асинхронная программа сталкивается с тем, что блокирует синхронную программу, асинхронная будет выполняться. Асинхронная программа будет “блокироваться” только тогда, когда нет задач на выполнение, поэтому такая программа называется неблокирующейся (non-blocking program). И каждое переключение с одной задачи на другую соответствует тому, что первая задача или выполнилась, или находится в месте, где она заблокирована. С большим количеством потенциально блокирующихся задач, асинхронная программа может превосходить синхронную тем, что проводит в целом меньше времени ожидая.

По сравнению с синхронной моделью, асинхронная модель лучше когда:

1. Когда есть много задач таких, что почти всегда есть по меньшей мере одна задача, которая может выполняться.

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

3. Задачи независимы друг от друга и не нуждаются во взаимодействии.

Эти условия почти полностью характеризуют типичный сетевой сервер (например, web сервер) в среде клиент-сервер. Каждая задача представляет собой один клиентский запрос с операциями ввода-вывода в форме получения запроса и отправления ответа. И клиентские запросы по большому счету независимы.

Так что реализация сетевого сервера является подходящим кандидатом для асинхронной модели, поэтому Twisted - это одна из первых и передовых сетевых библиотек.

1.4. Дальше и больше Это окончание первой части. Во второй части мы будем писать некоторые сетевые программы, обе - блокирующиеся и неблокирующиеся, настолько простые, насколько это возможно (без использования Twisted), для того, чтобы осознать как действительно работает асинхронная программа, написанная на Python’е.

2. Медленная поэзия и апокалипсис На этот раз мы запачкаем наши руки и напишем некоторый код. Но сначала - некоторые предположения.

2.1. Предположения о навыках Предполагается, что вы имеете некоторые навыки написания синхронных программ на Python’е и немного знаете о сокетном программировании на Python’е. Если вы никогда раньше не использовали сокеты, вы можете почитать документацию о Python-модуле socket, особенно примеры ближе к концу. Если вы никогда раньше не использовали Python, то все остальное вам, вероятно, покажется странным.

2.2. Предположения о компьютере Примеры разрабатывались и отлаживались на Linux’е. Возможно, что код работает под другие Unix подобные системы (Mac OSX или FreeBSD).

Далее предполагается, что вы установили относительно свежые версии Python и Twisted. Примеры разрабатывались с использованием Python 2.5 и Twisted 8.2.0.

Вы можете запускать все примеры на одном компьютере или сконфигурировать их для запуска по сети на нескольких машинах. Но для изучения основных механизмов асинхронного программирования использование одной машины предпочтительнее.

Код с примерами доступен ввиде zip или tar файла, или как git репозиторий. Если вы можете использовать git или другую систему контроля версий, которая может читать git репозиторий, то рекомендуется использовать этот метод, так как примеры время от времени обновляются, и в этом случае обновление будет осуществляться гораздо проще. Репозиторий помимо всего прочего включает исходники рисунков в формате SVG. Клонировать репозиторий можно следующим образом:

git clone git://github.com/jdavisp3/twisted-intro.git И последнее предположение: наличие нескольких консолей, открытых в директории с примерами (в одной из них предполагается открытым файл README).

2.3. Медленная поэзия Хотя процессоры намного быстрее, чем сети, а большинство сетей все же намного быстрее, чем мозг, или по меньшей мере быстрее, чем скорость движения глаз. Сложно получить заметную задержку в сети, особенно, в случае одной машины и байтов, проносящихся со свистом на loopback интерфейсе. То что нам нужно - медленный сервер, с искуственными задержками, которые мы можем менять для изучения их воздействия. Поскольку серверы должны что-то обслуживать, то наш сервер будет обслуживать поэзию. В исходниках примеров находится директория poetry с поэзией авторов John Donne, W.B. Yeats, Edgar Allen Poe. Конечно же, вы можете использовать свои собственные поэмы.

Медленный поэтический сервер реализован в blocking-server/slowpoetry.py. Вы можете запустить пример сервера следующим образом:

python blocking-server/slowpoetry.py poetry/ecstasy.txt Эта команда запустит блокирующий сервер с поэмой “Ecstasy” John Donne. Пойдем дальше и посмотрим на исходный код блокирующего сервера. Как можно заметить, в коде не используется Twisted, только основные сокетные операции. Сервер отправляет ограниченное количество байт за раз, с фиксированной временной задержкой между ними. По умолчанию, сервер отправляет 10 байт каждые 0.1 секунды, но вы можете поменять эти параметры с помощью опций командной строки --num-bytes и --delay. Например, чтобы отправлять 50 байт каждые 5 секунд, нужно запустить:

python blocking-server/slowpoetry.py --num-bytes 50 --delay 5 poetry/ecstasy.txt Когда сервер запускается, он печатает номер порта, который он слушает. По умолчанию, это первый доступный случайный порт на вашей машине. Запуская сервер с различными настройками, может понадобится использовать тот же порт, что и до этого, для того, чтобы не нужно было перенастраивать клиент.

Вы можете задать определенный порт следующим образом:

python blocking-server/slowpoetry.py --port 10000 poetry/ecstasy.txt Если у вас есть программа netcat (или nc), можно протестировать запущенный сервер следующим образом:

netcat localhost При запущенном сервере вы увидите медленно ползущую вниз по экрану поэму. Экстаз! Вы также заметите, что сервер печатает строки с количеством отосланных байт. Сразу после того, как поэма была отослана, сервер закрывает соединение.

По умолчанию, сервер слушает только локальный ”loopback” интерфейс. Если вы хотите получить доступ к серверу с другой машины, вы можете задать сетевой интерфейс через опцию –iface.

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

С другой стороны, если большинство пемиссемистичных людей из Peak Oil правы, и нашему миру грозит глобальный энергетический и социальный кризис, то, однажды, низкопропускной и маломощный сервер может стать именно тем, что нам нужно. Представьте, что после длительного дня забот о ваших садах, производстве собственной одежды, обслуживания Центрального Организационного Комитета Вашей комунны, борьбы с радиокативными зомби, бродящими по пост-апокалиптическим пустошам, вы сможете покрутить ваш генератор и скачать несколько строк высокой культуры из исчезнувшей цивилизации. Вот тогда наш сервер реально вcтупит в свои права.

2.4. Блокирующий клиент Также в исходных кодах можно найти пример блокирующего клиента, который скачивает поэмы из серверов одну за другой. Давайте дадим нашему клиенту три задачи для выполнения так, как это изображено на рисунке 1. Сначала мы запустим три сервера, обслуживающих три различных поэмы:

python blocking-server/slowpoetry.py --port 10000 poetry/ecstasy.txt --num-bytes python blocking-server/slowpoetry.py --port 10001 poetry/fascination.txt python blocking-server/slowpoetry.py --port 10002 poetry/science.txt Вы можете выбрать другие номера портов, если те, что выбраны выше, уже используются в вашей системе. Заметьте, что первый сервер использует chunk’и по 30 байт, вместо 10, что установлены по умолчанию, поскольку поэма, которую он обслуживает примерно в три раза длиньше остальных. При таких настройках скачивание каждой из них должно завершаться приблизительно в одно и тоже время.

Теперь мы можем запустить блокирующий клиент из blocking-client/get-poetry.py, для того, чтобы скачать немного поэзии. Запустите клиент следующим образом:

python blocking-client/get-poetry.py 10000 Вы также можете поменять номера портов, если сервера слушают на других портах. Поскольку мы имеем дело с блокирующим клиентом, то он будет скачивать одну поэму из каждого порта по очереди, ожидая пока поэма будет полностью получена до того, как начать скачивать следующую поэму. Вместо печати поэм, блокирующий клиент выводит нечто вроде:

Task 1: get poetry from: 127.0.0.1: Task 1: got 3003 bytes of poetry from 127.0.0.1:10000 in 0:00:10. Task 2: get poetry from: 127.0.0.1: Task 2: got 623 bytes of poetry from 127.0.0.1:10001 in 0:00:06. Task 3: get poetry from: 127.0.0.1: Task 3: got 653 bytes of poetry from 127.0.0.1:10002 in 0:00:06. Got 3 poems in 0:00:23. Это очень похоже на текстовую версию рисунка 1, где каждая задача качает одну поэму. В вашем выводе времена скачиваний могут немного отличаться. Ими можно вариьровать через параметр сервера --delay.

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

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

2.5. Асинхронный клиент Давайте теперь посмотрим на реализацию простого асинхронного клиента, написанного без ипользования Twisted. Сначала давайте его запустим. Запустите три сервера, как мы это делали выше. Если они все еще запущены, то мы можем снова их использовать. Теперь запустим асинхронный клиент, расположенный в async-client/get-poetry.py, как написано ниже:

python async-client/get-poetry.py 10000 В результате получится примерно такой вывод:

Task 1: got 30 bytes of poetry from 127.0.0.1: Task 2: got 10 bytes of poetry from 127.0.0.1: Task 3: got 10 bytes of poetry from 127.0.0.1: Task 1: got 30 bytes of poetry from 127.0.0.1: Task 2: got 10 bytes of poetry from 127.0.0.1: Task 1: 3003 bytes of poetry Task 2: 623 bytes of poetry Task 3: 653 bytes of poetry Got 3 poems in 0:00:10. На этот раз вывод намного длиньше, поскольку клиент выводит строку после каждого скачивания порции данных с любого сервера. Сервера, как и раньше, понемногу отдают поэзию. Заметьте, что выполнение отдельных задач чередуется, как это изображено на рисунке 3.

Попробуйте поменять опцию --delay для серверов (например, сделать один сервер медленнее, чем остальные) для того, чтобы увидеть как асинхронный клиент автоматически ”подстроится” под скорость медленных серверов, в тоже время успевая за быстрыми серверами. Это асинхронность в действии.

Также заметьте, что при найстройках сервера, которые были установлены выше, асинхронный клиент завершается примерно за 10 секунд, в то время как синхронному клиенту требуется примерно 23 секунды для получения всех поэм. Теперь вернемся к отличиям между рисунками 3 и 4. Тратя меньше времени на блокирование, наш асинхронный клиент может скачать все поэмы за более короткий промежуток времени, поскольку он переключается между всеми медленными серверами.

Технически, наш асинхронный клиент выполняет блокирующую операцию: он пишет в стандартный вывод, используя оператор print! Это не является проблемой в нашем случае. На локальной машине с терминальным shell’ом, готовым принять вывод, оператор print не будет реально блокироваться и будет выволняться досточно быстро по сравнению с нашими медленными серверами. Но, если бы мы захотели, чтобы наша программа была частью pipeline и все еще выполнялась асинхронно, нам нужно было бы использовать асинхронный ввод-вывод для стандартного ввода и вывода. Twisted имеет модули, реализующие это, но для простоты, мы будем использовать оператор print, даже в программах, написанных с использованием Twisted.

2.6. Пристальный взгляд Теперь давайте посмотрим на исходный код асинхронного клиента. Отметим основные отличия между ним и синхронным клиентом:

1. Вместо соединения только с одним сервером в один момент времени асинхронный клиент соединяется со всеми серверами одновременно.

2. Сокетные объекты, использованные для соединения, устанавливаются в неблокирующее состояние с использованием вызова setblocking(0).

3. Используется метод select из модуля select для ожидания (блокирования) до того момента, когда хотя бы один из сокетов готов отдать некоторую порцию данных.

4. При чтении данных из серверов мы читаем по максимуму до момента блокирования сокета, затем переходим к чтению данных из следующего имеющегося готового к чтению сокета.

Ядром асинхронного клиента является внешний цикл в функции get_poetry. Этот цикл можно разбить на несколько шагов:

1. Ожидание (блокирование) всех открытых сокетов с использованием select’а, до тех пор пока один или более сокетов будут иметь данные для считывания.

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

3. Повторять шаги выше до момента закрытия сокетов.

Синхронный клиент также имеет цикл (в функции main), но при каждой итерации цикла в синхронном клиенте скачивается полностью одна поэма. В то время как при каждой итерации асинхронного клиента скачиваются куски поэм. И мы не знаем, над какими из них мы будем работать в данной итерации, или сколько данных мы получим от каждой из них. Все это зависит от относительных скоростей серверов и состояния сети. Мы только позволяем select’у сообщать нам о том, какие сокеты готовы на чтение, и читаем столько данных, сколько можем из каждого сокета, не блокируясь.

Если бы синхронный клиент соединялся бы с фиксированным количеством серверов (скажем тремя), нам бы совершенно не понадобился внешний цикл, поскольку можно было бы последовательно вызвать три раза функцию get_poetry. Но асинхронный клиент не может обходиться без внешнего цикла, поскольку нужно одновременно опрашивать все сокеты и обрабатывать столько данных, сколько было доставлено за заданную итерацию.

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

Цикл является “reactor’ным”, поскольку он ожидает событий и затем реагирует на них. По этой причине, такой цикл известен также как ”event loop”. Поскольку реактивные системы зачастую ожидают вводавывода, такие циклы зачастую называют select циклы, поскольку вызов select используется для ожидания ввода-вывода. Таким образом в select циклах, ”событием” является момент, когда сокет становится доступным на чтение или запись. Надо отметить, что select - не единственный способ ожидания ввода-вывода, это один из самых старых (поэтому широко доступных) методов. Существует несколько более новых API, доступных на различных операционных системах, которые делают те же вещи, что и select, но имеют лучшую производительность. Но, невзирая на производительность, они все делают одно и тоже: берут множество сокетов (реально файловых дескрипторов) и блокируются до тех пор, пока один или более из них не станут готовы для операций ввода-вывода.

Нужно отметить, что можно использовать select и его товарищей для простой проверки на готовность множества файловых дескрипторов для операций ввода-вывода без блокирования. Это свойство позволяет реагирующей системе (reactive system) выполнять что-либо, не являющееся вводом-выводом, внутри цикла. Но в реагирующих системах в основном вся работа связана с вводом-выводом, таким образом блокирование на всех файловых дескрипторах сохранит ресурсы процессора.

Говоря точно, цикл в нашем асинхронном клиенте не соответсвует шаблону проектирования reactor, поскольку логика в цикле не реализована отдельно от основной логики, которая является специфичной для поэтических серверов. Они просто перемешаны друг с другом. Реальная реализация шаблона проектирования reactor реализовывала бы цикл как отдельную абстракцию, способную:

1. Принимать множество файловых дескрипторов для выполнения операций ввода-вывода.

2. Циклически сообщать о том, когда любой из файловых дескриторов готов на чтение или запись.

А реально хорошая реализация шаблона проектирования reactor делала бы:

1. Управление всеми проблемными случаями, которые происходят на различных системах.

2. Обеспечение множеством абстракций, помогающих использовать reactor с минимальными усилиями.

3. Предоставление реализаций основных протоколов, которые можно тут же использовать.

В целом, Twisted - это устойчивая, кросс-платформенная реализация шаблона проектирования reactor со множественными дополнениями. И в следующей главе мы начнем писать простые программы с использованием Twisted, так что сейчас мы движемся напрямую к получению поэзии со вкусом Twisted!

2.7. Упражнения 1. Проделайте несколько экспериментов с блокирующей и асинхронной версиями клиента, меняя количество и настройки поэтических серверов.

2. Может ли асинхронный клиент предоставить функцию get_poetry, которая возвращала бы текст поэмы? Почему нет?

3. Если бы вы хотели реализовать функцию get_poetry в асинхронном клиенте аналогичную синхронной версии, как бы это могло работать? Какие аргументы и возвращаемые значения были бы в этом 3. Наш первый взгляд на Twisted 3.1. Ничего не делать - путь Twisted На данный момент мы нацелены на улучшение кода асинхронного поэтического клиента, используя Twisted. Cначала давайте напишем несколько простых Twisted программ, чтобы осознать cуть вещей. Как было упомянуто в главе 2, примеры были отлажены с использованием Twisted 8.2.0. API в Twisted меняется, но основные API, которые мы собираемся использовать меняются медленно, поэтому примеры будут актуальны и для cледующих релизов. Если у вас не установлен Twisted, вы можете получить его здесь.

Самая простая Twisted программа представлена ниже. Код можно найти в директории с примерами в файле basic-twisted/simple.py.

from twisted.internet import reactor reactor.run() Пример запускается следующим образом:

python basic-twisted/simple.py В предыдущей главе мы выяснили, что Twisted - реализация шаблона проектирования Reactor, таким образом Twisted содержит объект, который представляет reactor или event loop, который является ядром любой Twisted программы. Первая строка нашей программы импортирует объект reactor, чтобы его можно было использовать, вторая строка запускает цикл реактора.

Программа выше ничего не делает. Остановить ее можно, нажав Ctrl-C, иначе она будет вечно ничего не делать. Обычно в цикле мониторятся на ввод-вывод какие-нибудь файловые дескрипторы (например, соединенные с поэтическим сервером). Позже, мы сделаем это, но сейчас цикл реактора простаивает.

Заметьте, что это не busy loop, который реально выполняет цикл. Если посмотреть на загруженность процесса, то не будет видно никаких всплесков. Фактически, наша программа совсем не использует процессор.

Вместо этого reactor простаивает наверху цикла рисунка 5, ожидая события, которое никогда не произойдет (в реальности, ожидая возврата вызова select без файловых дескрипторов).

Сделаем несколько выводов:

1. Цикл реактора в Twisted запускается явно вызовом reactor.run().

2. Цикл реактора запускается в том же треде, где он был запущен. В примере выше, он был запущен в единственном main треде.

3. После запуска цикл бесконечно продолжается.

4. Если нет задач, цикл реактора не потребляет процессор.

5. reactor явно не создается, он просто импортируется.

Последний пункт стоит обсудить. Twisted reactor - Singleton. Существует только один объект reactor, и он создается неявно при импорте. Если открыть модуль reactor в пакете twisted.internet, то кода там будет очень мало. Реальная реализация находится в другом файле:

(twisted.internet.selectreactor.py).

В Twisted есть много различных реализаций реактора. Как было упомянуто в главе 2, вызов select’а это один из способов ожидания событий на файловых дескрипторах. По умолчанию, Twisted использует select, но Twisted также включает и другие типы реакторов. Например, twisted.internet.pollreactor, который использует системный вызов poll вместо select.

Для использования другого реактора нужно установить его до импортирования twisted.internet.reactor.

Вот так можно установить pollreactor:

from twisted.internet import pollreactor pollreactor.install() Если проимпортировать twisted.internet.reactor без установки определенной реализации реактора, то Twisted установит reactor на основе select. По этой причине не импортируйте reactor в начале модулей, чтобы избежать случайной установки реактора по умолчанию, вместо этого импортируйте в том же месте, где собираетесь использовать.

Стоит заметить, что на момент написания статьи, Twisted двигается в сторону архитектуры, которая позволила бы использовать несколько реакторов одновременно. В такой схеме объект reactor мог бы подставляться как ссылка, а не как объект, импортированный из модуля.

Сейчас можно переписать нашу первую Twisted программу, используя pollreactor, которую можно найти в basic-twisted/simple-poll.py:

from twisted.internet import pollreactor pollreactor.install() from twisted.internet import reactor reactor.run() Таким образом, мы имеем poll цикл, который ничего не делает, вместо ничего не делающего select цикла.

Далее мы везде будем придерживаться реактора по умолчанию. Все реакторы Twisted делают одно и 3.2. Привет, Twisted Давайте сделаем программу Twisted, которая что-нибудь делает. Например, программу, печатающую сообщение в окне терминала, после запуска цикла реактора:

def hello():

print 'Hello from the reactor loop!' print 'Lately I feel like I\'m stuck in a rut.' from twisted.internet import reactor reactor.callWhenRunning(hello) print 'Starting the reactor.' reactor.run() Код программы находится в basic-twisted/hello.py. После запуска получится следующий вывод:

Starting the reactor.

Hello from the reactor loop!

Lately I feel like I'm stuck in a rut.

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

Заметьте, что функция hello вызывается после того, как reactor начал выполняться. Это означает, что функция вызывается реактором, то есть код Twisted должен вызывать эту функцию. Это происходит благодаря вызову метода реактора callWhenRunning с ссылкой на функцию, которую должен вызвать Twisted.

И, конечно же, мы должны вызвать callWhenRunning до того, как мы запустим reactor.

Термин callback означает ссылку на функцию (например, на функцию hello), которую мы передаем Twisted (или какой-то другой системе), которую Twisted будет использовать для того, чтобы ”вызывать” в определенное время. В примере выше такой вызов происходит сразу же после того, как reactor запустится.

Поскольку цикл в Twisted отделен от нашего кода, большинство взаимодействий между ядром реактора и логикой программы будет осуществляться через callback’и, которые мы передали Twisted, используя различные API.

Мы можем увидеть то, как Twisted вызывает наш код, используя следующую программу:

import traceback def stack():

print 'The python stack:' traceback.print_stack() from twisted.internet import reactor reactor.callWhenRunning(stack) reactor.run() Код находится в basic-twisted/stack.py, и он напечатает что-то вроде этого:

The python stack:

reactor.run() >sys.stderr, 'I am terribly sorry' print >>sys.stderr, 'try again later?' get_poetry(host, port, got_poem, poem_failed) reactor.run() Основной план здесь понятен:

1. Если мы получили поэму - напечатаем ее.

2. Если мы не получили поэму - напечатает ошибку.

3. В любом случае завершаем программу.

Синхронный аналог кода выше выглядит примерно так:

poem = get_poetry(host, port) # the synchronous version of get_poetry except Exception, err:

print >>sys.stderr, 'poem download failed' print >>sys.stderr, 'I am terribly sorry' print >>sys.stderr, 'try again later?' Таким образом callback подобен блоку else, и errback подобен except. Это означает, что вызов errback асинхронный аналог генерации исключения, и вызов callback’а соответсвует нормальному потоку программы.

Какие различия между двумя этими версиями? Первое: в синхронной версии интерпретатор Python’а будет гарантировать, что как только get_poetry сгенерирует любой exception, в любом случае будет выполняться блок except. Если мы верим интерпретатору, который запускает код Python’а, то мы можем довериться тому, что блок except будет выполнен вовремя.

Сравните с асинхронной версией: errback poem_failed вызывается нашим кодом методом clientConnectionFailed из PoetryClientFactory. Мы, а не Python, ответственны за то, чтобы код управления ошибками запустился в случае, если что-то пошло не так. Поэтому мы должны убедиться, что управляем каждой возможной ошибкой, вызывая errback с объектом типа Failure. Иначе наша программа застынет, ожидая callback, который никогда не придет.

Это показывает различие между синхронной и асинхронной версиями. Если мы не будем заботиться об отлавливании исключений в синхронной версии (не будем использовать try/except), интерпретатор Python’а поймает их за нас и прервет выполнение программы для того, чтобы показать ошибку. Но если мы не будем заботиться о вызове функции errback в PoetryClientFactory, наша программа будет вечно работать, не зная, что что-то не так.

Ясно, что управление ошибками в асинхронной программе - важное и хитрое дело. Вы могли бы сказать, что управление ошибками в асинхронном коде действительно важнее, чем управление нормальными случаями, так как ошибки могут случаться гораздо большими способами, чем не случаться. Забывать управлять ошибочными случами - общая ошбибка при программировании с помощью Twisted.

Еще один факт о синхронном коде выше: либо блок else один раз запустится, либо блок except запустится только один раз (в предположении, что синхронная версия get_poetry не входит в тело бесконечного цикла). Интерпретатор Python’а не решит внезапно запустить их оба или запустить блок else 27 раз. Тогда было бы совершенно невозможно программировать на Python’t, если бы это было так!

Снова, в асинхронном случае мы ответсвенны за запущенные callback или errback. Зная себя, мы можем сделать какие-нибудь ошибки. Мы могли бы вызвать callback или errback или вызвать callback 27 раз. Это было бы нежелательно для пользователей get_poetry. Подобно блокам else и except в операторе try/except, предполагается, что либо callback, либо errback запустится один раз для каждого определенного вызова get_poetry. Либо мы получим поэму, либо - нет.

Представьте попытку отладить программу, которая делает три поэтических запроса и получает семь вызовов callback’ов и два вызова errback’а. Где вы бы начали отлаживать? Вы, вероятно, закончили бы писать свои callback’и и errback’и для обнаружения, когда они вызываются во второй раз для одного и того же вызова get_poetry, и сгенерировали исключение в таком случае.

Еще одно наблюдение: обе версии имеют дублированный код. Асинхронная версия имеет два вызова reactor.stop и синхронная версия имеет два вызова sys.exit. Мы могли улучшить синхронную версию следующим образом:

poem = get_poetry(host, port) # the synchronous version of get_poetry except Exception, err:

print >>sys.stderr, 'poem download failed' print >>sys.stderr, 'I am terribly sorry' print >>sys.stderr, 'try again later?' sys.exit() Можем ли мы улучшить асинхронную версию подобным образом? Не очень ясно, что мы можем, так как callback и errback - две разные функции. Должны ли мы вернуться обратно к одному callback’у, чтобы сделать это возможным?

Хорошо, давайте вспомним то, что мы открыли о программировании с callback’ми:

1. Вызов errback’ов очень важен. Поскольку errback’и являются заменой блокам except, пользователям нужно их учитывать. Это не опциональное свойство нашего API.

2. Не вызывать callback’и в неправильный момент также важно как и вызывать их в правильный. Обычно, callback и errback взаимозаменяемы и вызываются только один раз.

3. Улучшать код может быть сложнее в случае использования callback’ов.

Мы скажем больше о callback’ах в следующих главах, но сейчас этого достаточно для того, чтобы увидеть почему Twisted может иметь управляющую абстракцию.

7.2. Deferred Поскольку callback’и много используются в асинхронном программировании, и поскольку их корректное использование, как мы увидели, может быть непростым, разработчики Twisted создали абстракцию, называемую Deffered, для того, чтобы упросить программирование с использованием callback’ов. Класс Deferred определен в twisted.internet.defer.

Слово ”deferred” (отложенный) - это либо глагол, либо прилагательное в повседневном английском, поэтому это может звучать немного странно при использовании его как существительного. Зная это, с этого момента, когда используется фраза ”deferred”, то это означает экземпляр класса Deferred. Мы обсудим то, почему класс называется Deferred в следующей главе. Нам может помочь добавление слова ”результат” к каждой фразе, то есть ”отложенный результат”. Как мы увидим, это действительно так.

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

Вновь созданный deferred имеет пустые цепочки. Мы можем заселить цепочки, добавляя callback’и и errback’и, и затем активизировать deferred с нормальным результатом (здесь ваша поэма!) или исключением (я не смог получить поэму, и вот почему). Активизированный deferred будет вызывать либо соответсвующие callback’и, либо errback’и в порядке, в котором они были добавлены. Рисунок 12 иллюстрирует экземпляр Deffered с его callback и errback цепочками:

Давайте опробуем это. Поскольку deferred’ы не используют reactor, мы можем их попробовать, не запуская цикла.

Вы могли бы заметить, что метод setTimeout класса Deferred использует reactor. Эта часть устарела, и, возможно, прекратит существовать в следующем релизе. Сделайте вид, что этого здесь нет и не используйте.

Наш первый пример находится в twisted-deferred/defer-1.py:

from twisted.internet.defer import Deferred def got_poem(res):

print 'Your poem is served:' def poem_failed(err):

d = Deferred() # add a callback/errback pair to the chain d.addCallbacks(got_poem, poem_failed) # fire the chain with a normal result d.callback('This poem is short.') print "Finished" Этот код создает новый deferred, добавляет пару callback/errback с методом addCallbacks, и затем активизирует цепочку с «нормальным результатом» с помощью метода callback. Запуститите код, и он напечатает следующее:

Your poem is served:

This poem is short.

Finished Это достаточно просто. Вот, что стоит отметить:

1. Подобно парам callback/errback, которые мы использовали в клиенте 3.1, callback’и, которые мы добавили к этому deferred’у, берут в качестве аргумента либо нормальный результат, либо ошибку. Оказывается, что deferred’ы поддерживают callback’и и errback’и с несколькими аргументами, но они всегда имеют по меньшей мере один аргумент: нормальный результат или результат с ошибкой.

2. Мы добавляем callback’и и errback’и в deferred парами.

3. Метод callback активизирует deferred с нормальным результатом, единственным аргументом метода.

4. Смотря на порядок вывода print’а, можно заметить, что активизация deferred’а вызывает callback’и немедленно. Здесь нет ничего асинхронного и не могло бы быть, поскольку reactor не запущен. Это реально сводится к обычному вызову функции в Python’е.

Хорошо, давайте нажмем на другую кнопку. Пример в twisted-deferred/defer-2.py активизирует errback цепочку deferred’а:

from twisted.internet.defer import Deferred from twisted.python.failure import Failure def got_poem(res):

print 'Your poem is served:' def poem_failed(err):

d = Deferred() # add a callback/errback pair to the chain d.addCallbacks(got_poem, poem_failed) # fire the chain with an error result d.errback(Failure(Exception('I have failed.'))) print "Finished" После запуска этой программы, мы получим вывод:

No poetry for you.

Finished Таким образом, активизация цепочки errback состоит только в вызове метода errback вместо callback, где в качестве аргумента использовали результат с ошибкой. Также как с callback’ми, errback’и вызываются сразу же после активизации.

В предыдущем примере мы передаем объект Failure к методу errback подобно тому, как мы делали это в клиенте 3.1. Это отлично, но deferred обертывает для нас обычные Exception в Failure. Мы можем увидеть это в twisted-deferred/defer-3.py:

from twisted.internet.defer import Deferred def got_poem(res):

print 'Your poem is served:' def poem_failed(err):

print err.class d = Deferred() # add a callback/errback pair to the chain d.addCallbacks(got_poem, poem_failed) # fire the chain with an error result d.errback(Exception('I have failed.')) Здесь мы подставляем обычный Exception в метод errback. В errback’е, мы печатаем класс и сам результат. Мы получаем такой вывод:

twisted.python.failure.Failure [Failure instance: Traceback (failure with no frames): : I have failed.

No poetry for you.

Это означает, что когда мы используем deferred’ы, мы можем вернуться обратно к работе с обычными исключениями, и объекты типа Failure создадутся для нас автоматически. deferred будет гарантировать, каждый errback вызывается с действительным экземпляром Failure.

Мы попробовали нажать на кнопку callback, и мы попробовали нажать на кнопку errback. Подобно любым хорошим инженерам, вы вероятно хотите нажать еще раз. Чтобы сделать код короче, мы будем использовать ту же функцию для обоих callback и errback. Только запомните, что они получают различные возвращаемые значения: один - результат, другой - ошибка. Посмотрите twisted-deferred/defer-4.py:

from twisted.internet.defer import Deferred def out(s): print s d = Deferred() d.addCallbacks(out, out) d.callback('First result') d.callback('Second result') print 'Finished' Теперь мы получаем этот вывод:

First result Traceback (most recent call last):

twisted.internet.defer.AlreadyCalledError Это интересно! deferred не позволяет нам активизировать нормальный результат во второй раз. Фактически, deferred не может быть активизирован во второй раз, что демонстируется в следующих примерах:

• twisted-deferred/defer-4.py • twisted-deferred/defer-5.py • twisted-deferred/defer-6.py • twisted-deferred/defer-7.py Заметьте, что последние операторы print никода не вызовутся. Методы calback и errback вызывают исключения, чтобы дать нам понять, что мы уже активизировали этот deferred. Deferred’ы помогают нам избежать ловушек, которые мы идентифицировали как повторный вызов. Когда мы используем deferred для управления нашими callback’ми, мы просто не можем сделать ошибку вызвав одновременно callback и errback, или вызывая callback 27 раз. Мы можем попробовать, но deferred сгенерирует исключение, вместо того, чтобы подставить нашу ошибку в сами callback’и.

Могут ли deferred’ы помочь нам улучшить асинхронный код? Рассмотрим пример в twisted-deferred/deferpy:

from twisted.internet.defer import Deferred def got_poem(poem):

from twisted.internet import reactor def poem_failed(err):

print >>sys.stderr, 'poem download failed' print >>sys.stderr, 'I am terribly sorry' print >>sys.stderr, 'try again later?' from twisted.internet import reactor d = Deferred() d.addCallbacks(got_poem, poem_failed) from twisted.internet import reactor reactor.callWhenRunning(d.callback, 'Another short poem.') reactor.run() Код выше - это наш первоначальный пример, с дополнительным запуском реактора. Заметьте, что мы используем callWhenRunning для активизации deferred’а после запуска реактора. Мы пользуемся тем фактом, что callWhenRunning принимает дополнительные позиционные и ключевые (keyword) аргументы для того, чтобы подставить их в callback во время его запуска. Многие Twisted API, регистрирующие callback’и, следуют этому соглашению, включая API для добавления callback’ов в deferred’ы.

И callback, и errback останавливают reactor. Поскольку deferred’ы поддерживают цепочки callback’ов и errback’ов, мы можем улучшить общий код, создав вторую ссылку в цепочках, что проиллюстрировано в twisted-deferred/defer-9.py:

from twisted.internet.defer import Deferred def got_poem(poem):

def poem_failed(err):

print >>sys.stderr, 'poem download failed' print >>sys.stderr, 'I am terribly sorry' print >>sys.stderr, 'try again later?' def poem_done(_):

from twisted.internet import reactor d = Deferred() d.addCallbacks(got_poem, poem_failed) d.addBoth(poem_done) from twisted.internet import reactor reactor.callWhenRunning(d.callback, 'Another short poem.') reactor.run() Метод addBoth добавляет одну и ту же функцию в обе цепочки callback и errback. И, в конце концов, мы можем улучшить асинхронный код.

Замечание: существует тонкость в способе, когда deferred действительно выполнял бы свою errback цепочку. Мы обсудим это в следующей главе, но запомните, что еще есть что изучить о deferred’ах.

7.3. Резюме В этой главе мы анализировали программирование с использованием callback’ов и идентифицировали некоторые потенциальные проблемы. Мы также увидели, как класс Deferred может выручить нас в следующих ситуациях:

1. Мы не можем игнорировать errback’и, так как они требуются для любого асинхронного API. Deferred’ы имеют встроенную поддержку errback’ов.

2. Вызов callback’ов несколько раз может вызвать зависание программы, и это трудно отлаживать. Deferred’ы могут активизироваться только один раз, что делает их похожими на семантику оператора try/except.

3. Программирование с помощью обычных callback’ов делает рефакторинг сложным. С помощью deferred’ов, мы можем рефакторить, добавляя ссылки в цепочку и перемещая код из одной ссылки в другую.

Мы еще не завершили рассмотрение deferred’ов, так как существует большое количество деталей их обоснования и поведения. Но теперь мы имеем достаточно для того, чтобы начать использовать их в нашем поэтическом клиенте, и мы сделаем это в следующей главе.

7.4. Упражнения 1. Последний пример игнорирует аргумент poem_done. Распечатайте его. Сделайте так, чтобы got_poem возвращал значение и посмотрите на изменения в аргументе poem_done.

2. Поменяйте два последних примера с deferred’ми и активизируйте errback цепочки. Убедитесь, что активизировали errback с Exception.

3. Прочитайте строки с документацией для методов addCallback и addErrback класса Deferred.

8. Отложенная поэзия 8.1. Клиент 4. Теперь, когда мы знаем что-то о deferred’ах, мы можем переписать наш Twisted поэтический клиент с их использованием. Вы можете найти клиент 4.0 в twisted-client-4/get-poetry.py.

Нашей функции get_poetry не нужны больше аргументы callback или errback. Вместо этого она возвращает новый deferred, к которому пользователь может прикрепить необходимые callback’и и errback’и.

def get_poetry(host, port):

Download a poem from the given host and port. This function returns a Deferred which will be fired with the complete text of the poem or a Failure if the poem could not be downloaded.

from twisted.internet import reactor factory = PoetryClientFactory(d) reactor.connectTCP(host, port, factory) Наш объект factory инициализируется с deferred’ом вместо пары callback/errback. Как только мы получили поэму или обнаружили, что не можем присоединиться к серверу, deferred активизируется либо поэмой, либо ошибкой:

class PoetryClientFactory(ClientFactory):

protocol = PoetryProtocol def init(self, deferred):

def poem_finished(self, poem):

def clientConnectionFailed(self, connector, reason):

Заметьте способ, которым особобождается ссылка на deferred после активизации. Этот подход можно найти в нескольких местах исходных кодов Twisted, и этот способ помогает убедиться в том, что мы не активизируем один и тотже deferred дважды. Это также упрощает работу сборщика мусора в Python’е.

И снова, нет необходимости менять PoetryProtocol, он хорош как есть. Все, что осталось - обновить функцию poetry_main:

def poetry_main():

addresses = parse_args() from twisted.internet import reactor def poem_failed(err):

print >>sys.stderr, 'Poem failed:', err if len(poems) + len(errors) == len(addresses):

d.addCallbacks(got_poem, poem_failed) Заметьте, что мы пользуемся способностью создавать цепочки вызовов в deferred’е для рефакторинга вызова poem_done из нашего первоначального callback’а и errback’а.

Поскольку deferred’ы используются повсеместно в Twisted, то распостраненной практикой является использование однобуквенной локальной переменной d для хранения deferred, над которым вы сейчас работаете. Для длительного хранения, подобно атрибутам объекта, зачастую используется название ”deferred”.

8.2. Обсуждение С нашим новым клиентном асинхронная версия get_poetry принимает ту же информация, что и асинхронная версия: только адрес поэтического сервера. Синхронная версия возвращает поэму, в то время как асинхронная версия возвращает deferred. Возвращать deferred - это типично для асинхронных API в Twisted и программах, написанных с помощью Twited, и это указывает на другой способ концептуализации deferred’ов:

Объект типа Deferred представляет ”асинхронный результат” или ”результат в пути”.

Мы можем сравнить эти два стиля программирования на рисунке 13:

Возвращая deferred, асинхронное API сообщает следующее пользователю:

Я асинхронная функция. Требуемый результат еще не получен. Но, когда он будет получен, я активизирую цепочку callback’ов этого deferred’а с результатом. С другой стороны, если что-то пошло не так, то вместо этого я активизирую цепочку errback этого deferred’а.

Конечно же, эта функция буквально сама не будет активизировать deferred, ее выполнение уже завершилось. Скорее, функция установила движение в цепочке событий, которые в конечном итоге приведут к активизации deferred’а.

Таким образом deferred’ы - способ ”сдвига по времени” результатов функций для приспосабливания к нуждам асинхронной модели. И deferred, возвращенный функцией, - это знак того, что функция асинхронная, воплощение будущего результата и обещание того, что результат будет доставлен.

В случае синхронной функции возвратить deferred возможно, так что технически возврат deferred’а означает, что функция потенциально асинхронная. В следующих главах мы увидим примеры синхронных функций, возвращающих deferred’ы.

Поскольку поведение deferred’ов хорошо определено и хорошо изучено (для людей, имеющих некоторый опыт программирования с Twisted), возвращая deferred’ы из наших собственных API, вы упрощаете другим Twisted программистам понимание и использование вашего кода. Без deferred’ов, каждая Twisted программа или даже каждый Twisted компонент, может иметь свой собственный уникальный метод для управления callback’ми, которые вам надо изучить, для того, чтобы их использовать.

8.3. Связь между deferred’ами, callback’ми, реактором При первом изучении Twisted, общераспостраненной ошибкой присваивать большую функциональность deferred’ам, нежели они в действительности имеют. Особенно, зачастую предполагается, что добавление функции в цепочку deferred’а, автоматически делает эту функцию асинхронной. Это могло бы навести на мысль, что вы могли бы использовать, скажем, os.system с Twisted добавляя его в deferred с помощью addCallback.

Думается, что эта ошибка вызвана попыткой изучить Twisted без первоначального изучения асинхронной модели. Поскольку типичный Twsisted код использует много deferred’ов, и только иногда ссылается на reactor, может показаться, что deferred’ы делают всю работу. Если вы читали введение с самого начала, то должно быть ясно, что это далеко от реальности. Хотя Twisted составлен из многих частей, которые работают вместе, первоначальная ответственность за реализацию асинхронной модели ложится на reactor.

Deferred’ы - полезная абстракция, но мы написали несколько версий нашего поэтического Twisted клинта без их использования.

Давайте посмотрим на stack trace в месте, когда вызывается наш первый callback. Запустите пример программы из twisted-client-4/get-poetry-stack.py с адресом запущенного поэтического сервера. Вы получите следующий вывод:

File "twisted-client-4/get-poetry-stack.py", line 129, in File "twisted-client-4/get-poetry-stack.py", line 122, in poetry_main... # some more Twisted function calls protocol.connectionLost(reason) File "twisted-client-4/get-poetry-stack.py", line 59, in connectionLost self.poemReceived(self.poem) File "twisted-client-4/get-poetry-stack.py", line 62, in poemReceived self.factory.poem_finished(poem) File "twisted-client-4/get-poetry-stack.py", line 75, in poem_finished d.callback(poem) # here's where we fire the deferred... # some more methods on Deferreds File "twisted-client-4/get-poetry-stack.py", line 105, in got_poem traceback.print_stack() Это очень похоже на stack trace, который мы создали для клиента 2.0. Визуализируем последний trace на рисунке 14:

И снова, это подобно нашим предыдущим Twisted клиентам, хотя визуальное представление становится неясным. Одно замечание, не касающееся рисунка: цепочка callback’ов выше не возвратит управление реактору до того, как не вызовется второй callback в deferred’е (poem_done), что произойдет сразу после возврата из первого callback’а (got_poem).

Есть еще одно отличие в нашем новом stack trace’е. Граница, разделяющая ”код Twisted” от ”нашего кода” немного размыта, поскольку deferred’ы - это в реальности Twisted код. Это чередование Twisted и пользовательского кода в callback-цепочке является общепринятым в большинстве программ, написанных с использованием Twisted, активно использующих различные Twisted абстракции.

Используя deferred, мы добавили несколько шагов в callback-цепочку, которая начинается в реакторе Twisted, но мы не поменяли фундаментальных механизмов асинхронной модели. Вспомним факты о программировании с использованием callback’ов:

1. Только один callback выполняется в один момент.

2. Когда выполняется reactor, наши callback’и - нет.

3. И наоборот.

4. Если наш callback блокируется, то вся программа блокируется.

Присоединение callback’а к deferred’у никак не меняет эти факты. В особенности, callback, который блокируется, все еще будет блокироваться, даже после присоединения к deferred’у. Поэтому deferred заблокируется при активизации (d.callback), поэтому все остальное тоже заблокируется. Поэтому мы заключаем, что:

Deferred’ы - решение (разработанное разработчиками Twisted) проблемы управления callback’ми. Они не являются ни способом мзбежать callback’ов, ни способом превратить блокирующиеся callback’и в неблокирующиеся.

Мы можем подтвердить наш последний вывод созданием deferred’а с блокирующм callback’ом. Рассмотрим пример кода в twisted-deferred/defer-block.py. Второй callback блокируется, используя функцию time.sleep. Если вы запустите этот скрипт и проверите порядок операторов print, то будет ясно, что блокирующийся callback также блокируется, находясь внутри deferred’а.

8.4. Резюме Возвращая Deferred, функция говорит пользователю ”Я асинхронная” и обеспечивает механизм (добавь здесь callback’и и errback’и!) получения асинхронного результата в момент, когда он прибудет. Deferred’ы повсеместно используются в Twisted, поэтому с ними надо ознакомиться и знать, как применять.

Клиент 4.0 - первая версия нашего Twisted поэтического клиента, которая действительно написана в стиле Twisted, используя deferred’ы в качестве возвращаемых значений асинхронного вызова функций.

Есть еще другие Twisted API, которые мы могли бы использовать для того, чтобы сделать код понятнее, но я думаю, что он представляет достаточно хороший пример того, как написать простые Twisted программы, по меньшей мере на стороне клиента. В конечном итоге, мы также перепишем наш поэтический сервер с использованием Twisted.

Но мы еще не окончили с deferred’ми. Для относительно небольшого куска кода, класс Deferred предоставляется удивительный ряд возможностей. Мы остановимся подоробнее на некоторых из этих возможностях и их мотифированности в следующей главе.

8.5. Упражнения 1. Обновите клиент 4.0 для установки timeout’а, в случае, если поэмы не была получена за заданный период времени. Активизируйте в этом случае errback deferred’а, используя пользовательское исключение. Не забудьте закрыть соединение.

2. Обновите клиент 4.1 для того, чтобы распечатать соответствующий адрес сервера при ошибки скачивания поэмы так, чтобы пользователь смог сказать какой сервер виновник. Не забудьте, что вы можете добавить дополнительные позиционные и keyword-аргументы, при присоединении callback’ов и errback’ов.

9. Deferred’ы, часть вторая 9.1. Дальнейшие выводы про callback’и Мы ненадолго приостановимся, чтобы снова подумать о callback’ах. Хотя сейчас мы достаточно знаем о deferred’ах, для того, чтобы писать асинхронные программы в стиле Twisted, класс Deferred предоставляет больше свойств, которые имеют значение в более сложных настройках. Поэтому мы придумаем более сложные настройки и посмотрим какие преимущества мы получаем при программировании с использованием callback’ов. Затем мы изучим то, как deferred’ы используют эти преимущества.

Для мотивации нашей дискуссии, добавим гипотетическое возможность к нашему поэтическому клиенту. Допустим, что некий профессор изобрел новый алгоритм, имеющий отношение к поэзии: Byronification Engine. Ловкий алгоритм берет на вход одну поэму и производит новую поэму, подобную первоначальной, но написанной в стиле Лорда Байрона. Помимо этого, наш профессор любезно предоставляет ссылку на реализацию на Python’е со следующим интерфейсом:

class IByronificationEngine(Interface):

def byronificate(poem):

Return a new poem like the original, but in the style of Lord Byron.

Raises GibberishError if the input is not a genuine poem.

Подобно большиству передовых программ, реализация имеет какие-то баги. Это означает, что в дополнение к документированным исключениям, метод byronificate иногда выкидывает произвольные исключения, когда натыкается на случаи, которые профессор забыл отловить.

Мы будем также предполагать, что движок выполняется достаточно быстро, поэтому мы можем просто его вызывать из основного треда, не беспокоясь о связывании с реактором. Вот как хочется, чтобы работала программа:

1. Попробовать скачать поэму.

2. Если скачивание не произошло из-за ошибки, сказать пользователю, что мы не можем получить поэму.

3. Если мы получили поэму, преобразовать ее с помощью движка Byronification.

4. Если движок выкинул исключение GibberishError, сказать пользователю, что мы не можем получить 5. Если движок выкинул какое-то другое исключение, сохранить первоначальную поэму.

6. Если мы имеем поэму - распечать ее.

7. Завершить программу.

Здесь идея в том, что GibberishError означает, что в конце концов мы не получили действительную поэму, поэтому мы просто скажем пользователю, что скачивание завершилось с ошибкой. Это не особо полезно при отладке, но наши пользователи просто хотят знать: мы получили поэму или нет. С другой стороны, если движок завершается с ошибкой по какой-то причине, то мы будем использовать поэму, которую мы получили из сервера. В конце концов, какая-нибудь поэзия лучше, чем никакой, даже если она не под торговой маркой ”стиль Байрона”.

Далее синхронная версия нашего кода:

poem = get_poetry(host, port) # synchronous get_poetry except:

print >>sys.stderr, 'The poem download failed.' poem = engine.byronificate(poem) except GibberishError:

print >>sys.stderr, 'The poem download failed.' print poem # handle other exceptions by using the original poem sys.exit() Это набросок программы мог бы быть проще с некоторыми улучшениями, но он достаточно ясно иллюстрирует логический поток. Мы хотим обновить наш последний поэтический клиент (который использует deferred’ы), чтобы реализовать эту же схему. Но мы не будем делать до следующей главы. Сейчас, вместо этого, давайте представим то, как мы могли бы сделать это с клиентом 3.1, нашим последний клиентом, который не использует deferred’ы. Предположим. что мы не заботимся управлением исключениями, но вместо этого мы меняем got_poem callback следующим образом:

def got_poem(poem):

poems.append(byron_engine.byronificate(poem)) Что происходит, когда метод byronificate генерирует GibberishError или какое-то другое исключение?

Смотря на рисунок 11, мы можем увидеть что:

1. Исключение будет передаваться к callback’у poem_finished в factory, методу, который в действительности вызывает callback.

2. Поскольку poem_finished не ловит исключения, далее продолжится выполнение в методе poemReceived 3. Затем в connectionLost также протокола.

4. Затем управление перейдет самому Twised, окончательно завершись в реакторе.

Как мы изучили, reactor ловит и логирует исключения, а не завершается с ошибкой. Но то, что он определенно не сделает - не скажет пользователю, что мы не смогли скачать поэму. reactor ничего не знает о поэмах или об исключениях типа GibberishError, это код общего назначения для всех видов сетевых взаимодейтсвий, даже не относящихся к поэзии.

Заметьте, как на каждом шаге в списке выше, исключение перемещается к коду, имеющему более и более общее назначение. В got_poem нет кода специфичного для нашего клиента управления исключениями.

Эта ситуация противоположна тому, как исключения распостраняются в синхронном коде.

Давайте посмотрим на рисунок 15, иллюстрацию стека вызова, которую мы можем увидеть в синхронном поэтическом клиенте:

Функция main - это ”высокоуровневая”, что означает, что она много знает о всей программе, почему она выходит, предполагаемое поведение в целом. Типично, main имела бы доступ к опциям командной строки, которая означает то, как пользователь хочет, чтобы программа работала (и, возможно, что надо делать, если что-то пошло не так).

С другой стороны, метод connect модуля socket, ”низкоуровневый”. Все что он знает - это то, что предполагается присоединиться по некоторому сетевому адресу. Он даже не знает, что на другом конце, или почему нам нужно соединиться прямо сейчас. Но connect - метод общего назначения, вы можете его использовать, невзирая на тип сервиса, к которому вы присоединяетесь.

А метод get_poetry находится по середине. Он знает, что он получает некоторую поэзию (и это единственное, чем он занимается), но не знает, что должно произойти, если он не сможет получить поэзию.

Таким образом, исключение, произошеднее в connect’е, будет перемещаться вверх по стеку, из низкоуровнего контекста и кода общего назначения в высокоуровневый контекст и кода специального назначения до тех пор, пока он не достигнет некоторого кода с контекстом, который будет знать, что делать, когда что-то пошло не так (или оно поймается интерпретатором Python’а и программа развалится).

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

Теперь вспомним гипотетическую модификацию клиента 3.1 выше. Стек вызова, который мы анализировали, изображен на рисунке 16, сокращенный только до нескольких функций:

Проблема теперь ясна: во время callback’а, низкоуровневый код (reactor) вызывает высокоуровневый код, который в свою очередь может вызвать более высокоуровневый код и так далее. Таким образом, если происходит исключение, и оно немедленно не обрабатывается примерно в том же stack frame’е, где оно произошло, то оно, вероятно, не будет обработано вовсе. Поскольку каждый раз, когда исключение перемещается вверх по стеку, оно перемещается к низкоуровневому коду, который еще менее вероятно знает, что делать.

Как только исключение перейдет в Twisted код, то все пропало. Исключение не будет обработано, оно будет только замечено (когда reactor окончательно его поймает). Поэтому, когда мы программируем с обычными callback’ми (без использования deferred’ов), мы должны тщательно отлавливать каждое исключение до того, как оно вовзвратится в Twisted, по меньшей мере, если мы хотим иметь какой-то шанс управления ошибками согласно нашим правилам. Это также касается ошибок, вызванных нашими багами!

Поскольку баг может присутствовать в любом месте нашего кода, нам нужно было бы оборачивать каждый callback, который мы пишем, в дополнительный уровень из операторов try/except так, чтобы исключения из наших нелепостей, могли быть обработаны. Тоже самое касается наших errback’ов, поскольку код для управления ошибками, может также иметь баги.

И это не особо хорошо.

9.2. Прекрасная структура Deferred’ов Оказывается, класс Deferred помогает решить нам эту проблему. В момент, когда deferred вызывает callback и errback, он ловит любое исключение, которое могло бы произойти. Другими словами, deferred действует как ”внешний уровень” из операторов try/except, так что, в конце концов, нам не нужно писать этот уровень, в случае использования deferred’ов. Но что делает deferred с исключением, которые он ловит? Все просто: он подставляет исключение (в виде Failure) следующему errback’у в цепочке.

Так как первый errback, который мы добавляем в deferred, является тем местом, где обрабатывается какое-нибудь условие ошибки, которое сигнализируется вызовом метода deferred’а errback. Второй errback будет обрабатывать исключение, возникшее или в первом callback’е, или в первом errback’e, и так далее вниз по цепочке.

Вспомним 12, визуальное представление deferred’а с некоторыми callback’ми и errback’ми в цепочке.

Давайте назовем первую пару callback/errback этапом 0, следующую - 1 и т.д.

В заданной стадии N, если callback или errback (какой бы ни выполнялся) завершаются с ошибкой, то будет вызван errback из этапа N+1 с соответсвующим объектом типа Failure, и callback из этапа N+1 не будет вызван.

Подставляя исключения, сгерерированных callback’ми, ”вниз по цепочке”, deferred перемещает исключения в направлеии ”высокоуровневого контекста”. Это также означает, что вызывающиеся методы deferred’а callback и errback никогда не сгенерируют исключение тому, кто их вызвал (конечно, если вы активизируете deferred только один раз!), поэтому низкоуровневый код может безопасно активизировать deferred, не заботясь об отлавливании исключений. В то время как, высокоуровневый код поймает исключения, добавляя errback’и в deferred (с помощью, например, addErrback).

В синхронном коде, исключения прекращают распостраняться после их первого отлавливания. А как же errback сигнализирует о том, что он ”поймал” ошибку? Также просто: не генерируя исключение. В этом случае, выполнение переключится на callback-цепочку. Так что если на заданном этапе N, либо callback, либо errback завершаются успешно (например, не генерируют исключение), то вызывается callback из этапа N+1 со значением, возвращенным из этапа N, и errback из этапа N+1 не вызывается.

Давайте подведем итоги того, что мы знаем об активизации deferred’а:

1. deferred содержит цепочку упорядоченных пар callback/errback (этапов). Пары находятся в порядке, в котором они добавлялись в deferred.

2. Этап 0, первая пара callback/errback, вызывается, когда активизируется deferred. Если deferred активизируется с методом callback, то вызывается callback из этапа 0. Если deferred активизируется методом errback, то вызывается errback из этапа 0.

3. Если на этапе N происходит ошибка, то вызывается errback из этапа N+1 с исключением (обернутым в Failure) в качестве первого аргумента.

4. Если этап N успешно завершается, то вызывается callback из этапа N+1 со значением, возвращенным этапом N+1, в качестве первого аргумента.

Этот шаблон проиллюстрирован на рисунке 17:

Зеленые линии показывают, что происходит, когда callback или errback успешно завершаются, и красные линии, если завершаются с ошибками. Линии показывают оба поток управления и исключений и возвращаемые значения вниз по цепочке. Рисунок 17 показывает все возможные пути, которыми deferred может быть пройти, но только один путь возьмется из определенного случая. Рисунок 18 показывает один возможный путь для ”активизации”:

На рисунке 18 вызывается метод callback deferred’а, который вызывает callback из этапа 0. Этот callback успешно завершается, поэтому управление (и возвращаемое значение из этапа 0) передается callback’у из этапа 1. Но callback из этапа 1 завершается с ошибкой (генерирует исключение), поэтому управление переключается на errback из этапа 2. errback ”обрабатывает” ошибку (не генерирует исключение), поэтому контроль переходит обратно к callback’у из этапа 3.

Заметьте, что любой путь из рисунка 17 будет проходить через каждый этап цепочки, но только один из пары callback/errback для каждой стадии будет вызван.

На рисунке 18, мы показали, что на этапе 3 callback успешно завершается, нарисовав выходящую зеленую стрелку, но поскольку нет больше этапов в deferred’е, то результат этапа 3 никуда в действительности не отправится. Если callback завершается успешно, то это в действительности не проблема, но что, если callback завершился с ошибкой? Если последний этап в deferred’е завершился с ошибкой, то мы говорим, что ошибка не обработывается, поскольку нет errback’а, который ее ”поймает”.

В синхронном коде необработанные исключения рушат интерпретатор, и необработанные исключение в обычных callback’ах асинхронного кода ловятся реактором и логируются. Что происходит с необработанными исключениями в deferred’ах? Давайте попробуем так сделать и посмотрим. Посмотрите на пример в twisted-deferred/defer-unhandled.py. Этот код активизирует deferred с одним callback’ом, который всегда генерирует исключение. Далее вывод программы:

Finished Unhandled error in Deferred:

Traceback (most recent call last):

--- --exceptions.Exception: oops Надо отметить следующее:

1. Последний оператор print выполняется, так что программа не рушится исключением.

2. Это означает, что traceback просто печатается, и это не разрушенный интерпретатор.

3. Текст traceback’а говорит нам, где deferred сам поймал исключение.

4. Сообщение ”Unhandled” печатается после ”Finished”.

Поэтому, когда мы используем deferred’ы, необработанные исключения в callback’ах все еще будут замечены для целей отладки, но они не будут рушить программу (фактически, они не будут передаваться реактору, deferred’ы первыми их поймают). Кстати, причина почему ”Finish” печатается первым связана с тем, что сообщение ”Unhandled” в действительности не печаетается до тех пор, пока deferred не утилизируется сборщиком мусора. Мы увидим причину в следующей главе.

В синхронном коде мы можем ”перевызвать” исключение, используя ключевое слово raise без аргументов. Делая так, мы вызываем первоначальное исключение, которое мы обрабатывали, что позволяет нам произвести какие дополнительные действия без полной его обработки. В свою очередь, мы может сделать тоже самое с errback’ом. deferred будет считать, что callback/errback завершился с ошибкой, если:

• callback/errback вызывает любой тип исключения, или • callback/errback возвращает объект типа Failure.

Поскольку первый аргумент errback’а всегда типа Failure, errback может ”перевызвать” исключение, возвратив свой первый аргумент, после выполнения какого-либо действия.

9.3. Callback’и и Errback’и, по двое Одно должно быть ясно из обсуждения выше - это то, что порядок, в котором вы добавляете callback’и и errback’и в deferred, определяет порядок, в котором они будут активизированы. Что еще должно быть ясно - это то, что в deferred’е callback’и и errback’и всегда появляются парами. Есть 4 метода в классе Deffered, которые вы можете использовать для добавления пар в цепочку:

1. addCallbacks 2. addCallback 3. addErrback 4. addBoth Очевидно, что первый и последний методы добавляют пару в цепочку. Но два других метода также добавляют пару callback/errback. Метод addCallback добавляет явный callback (тот, который вы передали в метод) и неявный ”сквозной” errback. Сквозная функция - это функция-заглушка, которая просто возвращает свой первый аргумент. Поскольку первый аргумент в errback’е всегда типа Failure, сквозной errback всгда завершается с ошибкой и отправляет ошибку следующему errback’у в цепочке.

Как вы несомненно догадались, функция addErrback добавляет явный errback и неявный сквозной callback.

И поскольку первый аргумент в callback’е никогда не является Failure, сквозной callback отправляет свой результат следующему callback’у в цепочке.

9.4. Deferred симулятор Хорошая идея познакомиться о том, как deferred’ы активизируют свои callback’и и errback’и. В twisteddeferred/deferred-simulator.py находится python скрипт, являющийся ”deferred симулятором”, небольшая программа, которая позволяет вам раскрыть то, как deferred’ы активизируют свои цепочки. Когда вы запустите скрипт, он попросит вам ввести список пар callback/errback в одну строку. Для каждого callback’а или errback’а вы задаете одно из:

• Он возвращает заданное значение (выполнение без ошибок) • Он вызывает данное исключение (выполнение с ошибкой) • Он возвращает свой аргумент (сквозное выполнение) После того, как вы ввели все пары, и вы хотите симулировать, скрипт напечатает диаграмму, показывающую содержимое цепочки и шаблоны активизации для методов callback и errback. Вы можете использовать терминальное окно, которое настолько широко, насколько это возможно, для того, чтобы увидеть все корректно. Вы также можете использовать опцию –narrow для распечатки диаграмма одну за другой, но проще увидеть их взаимосвязь при их распечатке бок о бок.

Конечно же, в реальном коде callback не будет возвращать одно и тоже значение каждый раз, и данная функция может иногда завершиться успешно, иногда - с ошибкой. Но симулятор может дать представление того, что произойдет для заданной комбинации результатов и ошибок, в заданной композиции callback’ов и errback’ов.

9.5. Резюме Немного больше подумав о callback’ах, мы осознали, что позволяя callback исключениям всплывать вверх по стеку, не приведет ни к чему хорошему, поскольку программирование с использованием callback’ов инвертирует обычную взаимосвязь между низкоуровневым и высокоуровневым кодом. Класс Deferred решает эту проблему, отлавливая исключения и отправляя из вниз по цепочке, вместо вверх, в reactor.

Мы также изучили, что обычные результаты (возвращаемые значение) также перемещаются вниз по цепочке. Комбинирую оба факта вместе, дают в результате шаблон активизации крест-накрест, так как deferred переключается между callback и errback цепочками, взависимости от результата на каждом этапе.

Вооружившись эти знанием, в следующей главе мы обновим наш поэтический клиент с некоторой поэтической трансформирующей логикой.

9.6. Упражнения 1. Изучите реализацию каждого из четырех методов в классе Deferred, которые добавляют callback’и и errback’и. Проверьте, что все методы добавляют пару callback/errback.

2. Используйте deferred симулятор для того, чтобы изучить отличие между этим кодом:

deferred.addCallbacks(my_callback, my_errback) deferred.addCallback(my_callback) deferred.addErrback(my_errback) Вспомните, что два последних метода добавляют неявные сквозные функции в качестве одного из 10. Преобразованная поэзия 10.1. Клиент 5. Теперь мы будем добавлять некоторую трансформирующую логику в наш поэтический клиент к строкам, предложенным в части 9, с использованием упрощенного преобразования - Cummingsifier. Cummingsifier алгоритм, который на вход берет поэму и возвращает поэму в стиле e.e.cummings. Далее алгоритм, который выполняет преoбразование:

def cummingsify(poem):

return poem.lower() К сожалению, этот алгоритм очень простой и, реально нем никогда не произойдет ошибки, поэтому в клиенте 5.0, расположенном в twisted-client-5/get-poetry.py, мы используем модифицированную версию cummingsify, которая произвольно выполняет следующее:

1. Возвращает трансформированную версию 2. Генерирует GibberishError 3. Генерирует ValueError Таким образом мы эмулируем более сложный алгоритм, который иногда может глючить.

Изменения в клиенте 5.0 также сделаны в функции poetry_main:

def poetry_main():

addresses = parse_args() from twisted.internet import reactor def try_to_cummingsify(poem):

def poem_failed(err):

print >>sys.stderr, 'The poem download failed.' if len(poems) + len(errors) == len(addresses):

d.addCallback(try_to_cummingsify) d.addCallbacks(got_poem, poem_failed) Таким образом, когда программа скачивает поэму c сервера, она будет выполнять одно из:

1. Печатать трансформированную версию поэмы 2. Печатать “Cummingsify failed!” и оригинальную версию поэмы 3. Печатать “The poem download failed.” Хотя мы сохранили возможность скачивать с нескольких серверов, когда вы будете проверять клиент 5.0, проще использовать один сервер и запускать программу несколько раз, до тех пор пока вы не увидите различные выводы. Также попробуйте запустить клиент с портом, на котором не запущен сервер.

Давайте нарисуем цепочку callback/errback, которые создаются для каждого Deferred’а, создаваемого в функции get_poetry:

Обратите внимание, что pass-through добавляется неявно в errback функцией addCallback. Функция pass-through ничего не делает и передает свой аргумент типа Failure следующему errback’у (poem_failed).

Таким образом, poem_failed может управлять ошибками из двух функций: get_poetry (в этом случае deferred poem_failed активизируется предыдущим deferred’ом из цепочки errback) и функцией cummingsify.

Нужно заметить, что великолепная тень на рисунке 19 была сделана в Inkscape1.

Давайте проанализируем различные способы, которые могут активизировать наши deferred’ы. Случай, когда мы получаем поэму и функция cummingsify работает корректно, изображен на рисунке 20:

В этом случае, ни в одном callback’е не произошло ошибок, так что управление полностью проходит вниз по линии callback. Заметим, что функция poem_done получает None в качестве аргумета, поскольку got_poem в действительности не возвращает значение. Если мы хотим, чтобы каждая функция из линии callback имела доступ к поэме, то нам нужно поменять функцию got_poem так, чтобы она возвращала поэму.

Рисунок 21 иллюстрирует случай, когда мы получием поэму, но в функции cummingsify генерирует исключение GibberishError:

После того как try_to_cummingsify callback генерирует во второй раз ислючение GibberishError, управление переключается на линию errback и вызывается poem_failed с исключением в качестве своего параметра (естественно, обернутого в Failure).

И, так как poem_failed не вызывает исключение, или не возвращает Failure, то после того, как она завершит работу, контроль переключается обратно на линии callback. Если мы предполагаем, что poem_failed 1 http://inkscape.org/ полностью управляет ошибками, то возвратить None - это разумное поведение. С другой стороны, если мы хотим, чтобы poem_failed производила некоторое дейтсвие, но все еще передавала ошибку, мы могли бы поменять poem_failed так, чтобы она возвращала свой аргумент err и, обработка продолжилась бы ниже по линии errback.

Заметим, что ни в got_poem, ни в poem_failed никогда не происходит ошибок, так что функция poem_done для errback никогда не будет вызвана. Но, безопасней добавить добавить такую функцию в errback, чтобы явится примером “оборонительного” программирования, так как или got_poem, или poem_failed могут иметь ошибки, о которых мы не знаем. Так как метод addBoth гарантирует, что определенная функция запустится независимо от того как deferred был активизирован (ошибкой или результатом), использование addBoth является аналогичным добавлению finally в оператор try/except.

Теперь исследуем случай, который изображен на рисунке 22, когда мы скачали поэму, и функция cummingsify сгнерировала исключение ValueError.

Эта ситуация аналогична ситуации на рисунку 20, за исключением, что got_poem получает оригинальную версию поэмы вместо трансформированной версии. Переключение происходит полностью внутри callback’а try_to_cummingsify, который отлавливает исключение ValueError обычным оператором try/except и возвращает оригинальную поэму. Объект deferred не обнаруживает ошибки.

И наконец, рисунок 23 - мы рассматриваем случай, когда мы пытались скачать поэму из несуществующего сервера:

Как и прежде, poem_failed возвращает None, поэтому далее контроль переключается на линию callback.

10.2. Клиент 5. В клиенте 5.0 мы отлавливаем исключение из функции cummingsify в нашем callback’е try_to_cummingsify, используя обычный оператор try/except, не позволяя deferred’у ловить его первым. Такой подход не является обязательно ошибычным, но является поучительным рассмотреть как мы можем сделать это по-другому.

Давайте предположим, что мы хотим позволить deferred’у ловить оба исключения GibberishError и ValueError и отправлять их по цепи errback. Для того, чтобы сохранить поведение нашей последовательности errback, нужно проверить: если мы видим, что ошибка ValueError, то нужно возвратить оригинальную поэму, так чтобы управление вернулось обратно на цепь callback и, чтобы оригинальная поэма напечаталась.

Но здесь есть проблема: errback не получил бы оригинальную поэму, вместо этого получил бы завернунутое в Failure исключение ValueError, сгенерированное функцией cummingsify. Для того, чтобы позволить errback управлять ошибкой, нам нужно сделать так, чтобы errback получал оригинальную поэму.

Один из способов сделать такое - это поменять функцию cummingsify так, чтобы оригинальная поэма была добавлена в исключение. Это то, что мы сделали в клиенте 5.1, расположенном в twisted-client-5/getpoetry-1.py. Мы поменяли исключение ValueError на специальное исключение CannotCummingsify, которое принимает оригинальную поэму в качестве своего первого аргумента.

Если бы cummingsify была бы реальной функцией во внешнем модуле, то, вероятно, лучшим решением было бы обернуть ее другой функцией, которая отлавливала бы любое исключение кроме GibberishError и генерировала бы исключение CannotCummingsify. С нашей новой структурой, функция poetry_main выглядит так:

def poetry_main():

addresses = parse_args() from twisted.internet import reactor def cummingsify_failed(err):

if err.check(CannotCummingsify):

def poem_failed(err):

print >>sys.stderr, 'The poem download failed.' if len(poems) + len(errors) == len(addresses):

d.addCallback(cummingsify) d.addErrback(cummingsify_failed) d.addCallbacks(got_poem, poem_failed) Рис. 22: Случай, когда мы скачали поэму и в cummingsify произошла ошибка И каждый deferred имеет структуру как на рисунке 24:

Исследуем cummingsify_failed errback:

def cummingsify_failed(err):

if err.check(CannotCummingsify):

Мы используем метод check объектов типа Failure для проверки является ли исключение, встроенное в Failure экземпляром типа CannotCummingsify. Если это так, то мы возвращаем первый аргумент исключения (изначальную поэму) и, таким образом управляем ошибкой. Поскольку возвращаемое значение не является Failure, то контроль возвращается цепи callback. Иначе, мы возвращаем Failure и отправляем ошибку далее по цепи errback. Как вы можете это видеть, исключение доступно как значение атрибута объекта Failure.

На рисунке 25 показано что случится, если мы получим исключение CannotCummingsify:

Таким образом, когда мы используем deferred’ы, иногда мы можем выбрать хотим ли мы использовать операторы try/except для управления исключениями или позволить deferred’м отправлять ошибки в errback.

10.3. Резюме В главе 10 мы обновили наш поэтический клиент для того, чтобы использовать возможность Deferred маршрутизовать ошибки и результаты по цепи callback’в и errback’в. Хотя пример был достаточно искусственным, он проиллюстрировал как контролирующий поток в deferred’х переключается между callback и errback цепями взависимости от результата на каждой стадии.

Так что теперь мы знаем все, что можно знать про deferred’ы, верно? Нет еще! Мы будем исследовать остальные особенности deferred’в в следующей части. Но сначала, мы поедем немного в объезд и, в главе 11, реализуем Twisted версию нашего поэтического сервера.

10.4. Упражнения 1. На рисунке 25 показан один из четырех возможных способах активизации deferred’а в клиенте 5.1.

Нарисуйте три других.

2. Используйте deferred simulator для эмулирования всех возможных активизаций для клиетов 5.0 и 5.1.

Для начала, эта программа может представлять случай, где функция try_to_cummingsify успешно завершается в клиенте 5.0:



Pages:     || 2 | 3 |


Похожие работы:

«Государственное образовательное учреждение высшего профессионального образования Московской области Международный университет природы, общества и человека Дубна (Университет Дубна) Институт системного анализа и управления Кафедра системного анализа и управления УТВЕРЖДАЮ проректор по учебной работе С.В. Моржухина __20 г. ПРОГРАММА ДИСЦИПЛИНЫ ОСНОВЫ ДИСКРЕТНОЙ МАТЕМАТИКИ (наименование дисциплины) по направлению 010400 62 — Информационные технологии (№, наименование направления, специальности)...»

«Программа фундаментальных исследований Секции языка и литературы ОИФН РАН Язык и литература в контексте культурной динамики Отчеты по проектам за 2013 год Координаторы Программы: акад. РАН А.Б.Куделин, чл.-корр. РАН В.А.Виноградов, акад. РАН Н.Н.Казанский Направление I. Типологическое и историческое изучение языковых явлений в их соотношении с культурной эволюцией, реконструкция культуры по данным языка (кураторы – чл.-корр. РАН А.В.Дыбо, чл.-корр. РАН В.А.Плунгян) Лексика традиционной духовной...»

«Математические вопросы синтеза интегральных схем С.А.Ложкин, А.М.Марченко Общая характеристика и аннотация Общая характеристика курса Курс Математические вопросы синтеза интегральных схем является обязательным для всех студентов специальности 01.02.09.01 (математическая кибернетика). Он читается в 7 семестре в объеме 32 часов лекций, сопровождаемых 16 часами практических занятий, и завершается зачетом. Чтение курса обеспечивается кафедрой математической кибернетики при поддержке фирмы Интел. В...»

«Утверждаю Председатель ВЭС В.Д. Шадриков ОТЧЁТ О РЕЗУЛЬТАТАХ НЕЗАВИСИМОЙ ВНЕШНЕЙ ОЦЕНКИ КАЧЕСТВА ОБРАЗОВАНИЯ ОСНОВНАЯ ОБРАЗОВАТЕЛЬНАЯ ПРОГРАММА ПО СПЕЦИАЛЬНОСТИ 130405.65 ОБОГАЩЕНИЕ ПОЛЕЗНЫХ ИСКОПАЕМЫХ ГОУ (НОУ) ВПО Кузбасский государственный технический университет имени Т.Ф. Горбачева Менеджер проекта: _/ А.Л. Дрондин, к.п.н. _2012 г. Эксперты АККОРК: _/ А.Л.Вильмис, к.т.н. _2012 г. _/ И.А.Пухальский _2012 г. Москва – ОГЛАВЛЕНИЕ ОГЛАВЛЕНИЕ _ РЕЗЮМЕ ОСНОВНЫЕ ВЫВОДЫ ПО ЭКСПЕРТИЗЕ КАЧЕСТВА...»

«МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ СБОРНИК примерных программ математических дисциплин цикла МиЕН Федерального государственного образовательного стандарта высшего профессионального образования 3-его поколения Москва 2008 СОДЕРЖАНИЕ Пояснительная записка. 1. Математические компетенции бакалавра. 2. 3. Комплекты программ математических дисциплин. 3.1. Программы математических дисциплин в образовательной области Техника и технология (УГС 090000, 200000-230000). 3.2. Программы...»

«Негосударственное образовательное учреждение дополнительного профессионального образования Ассоциация иностранных языков Рассмотрено на заседании Утверждено приказом учебно-методическогосовета. №15_ от 3_ октября 2013 г. Протокол №2_ от 1_ октября_ 2013 г. РАБОЧАЯ ПРОГРАММА полного академического курса испанского языка (2 года, 4 семестра, 510 академических часов) _ Ф.И.О. учителя на 20 - 20 учебный год Санкт-Петербург 2013 год РАБОЧАЯ ПРОГРАММА ПОЛНОГО АКАДЕМИЧЕСКОГО КУРСА ИСПАНСКОГО ЯЗЫКА...»

«Правительство Российской Федерации Санкт-Петербургский государственный университет Геологический факультет РАБОЧАЯ ПРОГРАММА УЧЕБНОЙ ДИСЦИПЛИНЫ БИОМИНЕРАЛОГИЯ И ОРГАНИЧЕСКАЯ МИНЕРАЛОГИЯ (Дополнительные главы) специальная дисциплина образовательной программы подготовки аспиранта специальность 25.00.05 - минералогия, кристаллография Язык(и) обучения русский Трудоёмкость зачётных единиц 2 Регистрационный номер рабочей программы: / / Санкт-Петербург Раздел 1. Характеристики, структура и содержание...»

«Москва, 15 февраля 2011 г. www.vishnevskogo.ru Российское общество эндоскопических хирургов (РОЭХ) Институт хирургии им. А.В.Вишневского Сателлитный симпозиум с международным участием и живой демонстрацией из операционной НОВЫЕ ВОЗМОЖНОСТИ В ХИРУРГИИ ЕДИНОГО ЛАПАРОСКОПИЧЕСКОГО ДОСТУПА ПРОГРАММА ПРИГЛАШЕНИЕ При поддержке ООО ОЛИМПАС МОСКВА Москва, 15 февраля 2011 г. ПРЕДСЕДАТЕЛИ ЭКСПЕРТЫ ОРГ. КОМИТЕТ Профессор С.И. Емельянов, Председатели: Профессор А.В. Федоров, Профессор Ю.Г. Старков,...»

«ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ САРАТОВСКАЯ ГОСУДАРСТВЕННАЯ ЮРИДИЧЕСКАЯ АКАДЕМИЯ УТВЕРЖДАЮ Первый проректор, Проректор по учебной работе _ С.Н. Туманов _2012 г. УЧЕБНО-МЕТОДИЧЕСКИЙ КОМПЛЕКС ДИСЦИПЛИНЫ СПЕЦИАЛИЗАЦИИ КРИМИНАЛИСТИЧЕСКАЯ РЕГИСТРАЦИЯ (для студентов 2 курса очной формы обучения Института юстиции) по направлению подготовки (специальности) 031003.65 – судебная экспертиза Саратов Учебно-методический комплекс...»

«ФЕДЕРАЛЬНОЕ АГЕНТСТВО СВЯЗИ ФЕД ЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ ОБРАЗОВ АТЕЛЬНОЕ БЮДЖЕТНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВ АНИЯ САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ТЕЛЕКОММУНИКАЦИЙ им. проф. М. А. БОНЧ-БРУЕВИЧА М ЕЖ ДУНА РО Д НАЯ Н АУЧНО- ТЕХНИЧЕСКАЯ И Н АУ ЧНО- МЕТОДИЧЕСК АЯ КОНФЕРЕНЦИЯ АКТУАЛЬНЫЕ ПРОБЛЕМЫ ИНФОТЕЛЕКОММУНИКАЦИЙ В НАУКЕ И ОБРАЗОВАНИИ № 20–24 февраля 2012 года ПРИГЛАСИТЕЛЬНЫЙ БИЛЕТ И ПРОГРАММА СПб ГУТ)))

«МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ Государственное образовательное учреждение высшего профессионального образования САМАРСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ Факультет _механико-математический (наименование) Кафедра _математического моделирования в механике (наименование) УТВЕРЖДАЮ Проректор по учебной работе В.П. Гарькин _ 2011 г. РАБОЧАЯ ПРОГРАММА ДИСЦИПЛИНЫ Вычислительный эксперимент в аэромеханике Профессиональная образовательная программа специальности 010800 Механика и...»

«1. Общие положения Настоящее положение определяет порядок разработки и утверждения рабочей программы учебной дисциплины, ее форму и содержание. Рабочая программа – нормативный документ, определяющий объем, содержание, порядок изучения и преподавания какой-либо дисциплины; регламентирует как деятельность преподавателей, так и учебную работу обучающихся. Рабочая программа устанавливает количество часов, отводимых на ее изложение и изучение в различных формах образовательного процесса (лекции,...»

«РАБОЧАЯ ПРОГРАММА УЧЕБНОЙ ДИСЦИПЛИНЫ ОП. 04 Информационные технологии в профессиональной деятельности 2 Рабочая программа учебной дисциплины разработана на основе Федерального государственного образовательного стандарта (далее – ФГОС) по специальности (специальностям) среднего профессионального образования (далее - СПО) 260870 Технология продукции общественного питания Организация-разработчик: ГБОУ СПО Комаричский механико-технологический техникум п.Комаричи Разработчики: Маруева О.А.-...»

«СПРАВКА о наличии учебной, учебно-методической литературы и иных библиотечно-информационных ресурсов и средств обеспечения образовательного процесса, необходимых для реализации заявленных к лицензированию образовательных программ Федеральное государственное образовательное учреждение высшего профессионального образования Российский государственный аграрный заочный университет, ФГОУ ВПО РГАЗУ Владимирский филиал Федерального государственного образовательного учреждения высшего профессионального...»

«Основная образовательная программа по направлению подготовки 210700 ИНФОКОММУНИКАЦИОННЫЕ ТЕХНОЛОГИИ И СИСТЕМЫ СВЯЗИ составлена на основании ФГОС ВПО по направлению подготовки 210700 ИНФОКОММУНИКАЦИОННЫЕ ТЕХНОЛОГИИ И СИСТЕМЫ СВЯЗИ (ПРИКАЗ от 22 декабря 2009 г. N 785 Об утверждении и введении в действие федерального государственного образовательного стандарта высшего профессионального образования по направлению подготовки 210700 ИНФОКОММУНИКАЦИОННЫЕ ТЕХНОЛОГИИ И СИСТЕМЫ СВЯЗИ (КВАЛИФИКАЦИЯ...»

«ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ УХТИНСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ В.И. Солдатенков МАТЕРИАЛЫ И МАШИНЫ ДЛЯ СТРОИТЕЛЬСТВА ЛЕСОВОЗНЫХ ДОРОГ Часть 2 Допущено в качестве учебного пособия для студентов вузов, обучающихся по направлению подготовки дипломированного специалиста 656300 Технология лесозаготовительных и деревообрабатывающих производств по специальности 260100 Лесоинженерное дело Ухта 2005 УДК 62.08 С 60 Солдатенков, В.И. Материалы и машины для строительства лесовозных...»

«МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ государственное образовательное учреждение высшего профессионального образования Тобольский государственный педагогический институт им. Д.И.Менделеева Утверждаю: Зав.кафедрой БЖД/МБД Манакова И.Н.. Учебно-методический комплекс по дисциплине Основы медицинских знаний и здорового образа жизни Специальность 05020102.65 – Математика, специализация Алгебра и геометрия Подготовила УМК ассистент кафедры БЖД /МБД: Аптыкова Г.Ш. Тобольск...»

«Приложение 2: Программа-минимум кандидатского экзамена по истории и философии науки ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ ПЯТИГОРСКИЙ ГОСУДАРСТВЕННЫЙ ЛИНГВИСТИЧЕСКИЙ УНИВЕРСИТЕТ Утверждаю _ Проректор по научной работе и развитию интеллектуального потенциала университета профессор З.А. Заврумов _2012 г. ПРОГРАММА-МИНИМУМ кандидатского экзамена История и философия науки по специальности 23.00.04 Политические проблемы международных...»

«Апрель - Июнь, 2009. #40 новостиЦАЗ Форум для тех, кому небезразлично будущее сельского хозяйства в Центральной Азии и Стр. 5 Закавказье СТР. Апрель - Июнь, 2009. #40 1 Содержание 4 Важные события 6 Новости наук и 8 Встречи и Конференции Укрепление потенциала Семинары и полевые дни Укрепление связей с НСХИ Публикации Программа КГМСХИ по устойчивому развитию сельского хозяйства в Центральной Азии и Закавказье Программа КГМСХИ по устойчивому развитию сельского хозяйства в Центральной Азии и...»

«Министерство образования и науки Российской Федерации Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования Саратовская государственная юридическая академия УТВЕРЖДЕНО на заседании Ученого Совета ФГБОУ ВПО СГЮА протокол № 6 от 20 марта 2014 года ПРОГРАММА ВСТУПИТЕЛЬНОГО ЭКЗАМЕНА ПО ФИЛОСОФИИ Саратов 2014 Вопросы к вступительному экзамену по философии Понятие мировоззрения. Структура мировоззрения. 1. Миф и религия как исторические типы...»






 
2014 www.av.disus.ru - «Бесплатная электронная библиотека - Авторефераты, Диссертации, Монографии, Программы»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.