Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Python
Code

Асинхронный ввод-вывод с Python 3

by
Difficulty:AdvancedLength:MediumLanguages:

Russian (Pусский) translation by Ilya Nikov (you can also view the original English article)

В этом руководстве вы познакомитесь с возможностями асинхронного ввода-вывода, представленными в Python 3.4 и улучшенными в Python 3.5 и 3.6.

Ранее в Python было мало отличных вариантов для асинхронного программирования. Новая поддержка Async I/O, наконец, обеспечивает первоклассную поддержку, которая включает в себя как высокоуровневые API, так и стандартную поддержку, которая нацелена на объединение нескольких сторонних решений (Twisted, Gevent, Tornado, asyncore и т.д.).

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

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

Pluggable Event Loops

Основной концепцией асинхронного ввода-вывода является цикл обработки событий. В программе может быть несколько циклов событий. Каждый поток будет иметь не более одного активного цикла обработки событий. Цикл обработки событий предоставляет следующие возможности:

  • Регистрация, выполнение и отмена отложенных вызовов (с таймаутами).
  • Создание клиентских и серверных транспортов для различных видов связи.
  • Запуск подпроцессов и связанных транспортов для связи с внешней программой.
  • Делегирование дорогостоящих вызовов функций в пул потоков.

Быстрый пример

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

Класс AbstractEventLoop предоставляет базовый контракт для циклов событий. Есть много вещей, которые должен поддерживать цикл обработки событий:

  • Планирование функций и подпрограмм для выполнения
  • Создание фьючеров и задач
  • Управление TCP-серверами
  • Обработка сигналов (в Unix)
  • Работа с пайпами и подпроцессами

Вот методы, связанные с выполнением и остановкой события, а также планирование функций и подпрограмм:

Подключение нового цикла событий

Asyncio разработан для поддержки нескольких реализаций циклов событий, которые придерживаются его API. Ключевым является класс EventLoopPolicy, который настраивает asyncio и позволяет контролировать каждый аспект цикла событий. Вот пример пользовательского цикла обработки событий, под названием uvloop, основанного на libuv, который должен быть намного быстрее, чем альтернативы (я сам не тестировал его):

Вот и все. Теперь, когда вы используете какую-либо функцию asyncio

Сопрограммы, фьючеры и задачи

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

Если вы вызываете такую функцию, она не запускается. Вместо этого он возвращает объект сопрограммы, и если вы не запланируете его выполнение, вы также получите предупреждение:

Чтобы фактически выполнить сопрограмму, нам нужен цикл обработки событий:

Это прямое планирование. Вы также можете построить цепочку сопрограмм. Обратите внимание, что вы должны вызывать await при вызове сопрограмм:

Класс asyncio Future аналогичен классу concurrent.future.Future. Он не является потоко-безопасным и поддерживает следующие функции:

  • добавление и удаление готовых обратных вызовов
  • отмена
  • настройка результатов и исключений

Вот как использовать future с циклом событий. Сопрограмма take_your_time() принимает future и устанавливает его результат после сна на секунду.

Функция sure_future() планирует сопрограмму, а wait_until_complete() ожидает выполнения future. За кулисами она добавляет готовый обратный вызов в future.

Это довольно громоздко. Asyncio предлагает задачи, чтобы сделать работу с фьючерами и сопрограммами более приятной. Задача - это подкласс Future, который оборачивает сопрограмму и которую можно отменить.

Сопрограмма не должна принимать явное future и устанавливать его результат или исключение. Вот как выполнить те же операции с задачей:

Транспорты, протоколы и потоки

Транспорт - это абстракция канала связи. Транспорт всегда поддерживает определенный протокол. Asyncio предоставляет встроенные реализации для TCP, UDP, SSL и каналов подпроцесса.

Если вы знакомы с сетевым программированием на основе сокетов, вы будете чувствовать себя как дома с транспортными средствами и протоколами. С Asyncio вы получаете асинхронное сетевое программирование стандартным способом. Давайте посмотрим на печально известный эхо-сервер и клиент («привет мир» сетей).

Во-первых, клиент echo реализует класс EchoClient, производный от asyncio.Protocol. Он сохраняет свой цикл обработки событий и сообщение, которое он отправит на сервер при подключении.

В обратном вызове connection_made() записывает свое сообщение в транспорт. В методе data_received() он просто печатает ответ сервера, а в методе connection_lost() останавливает цикл обработки событий. При передаче экземпляра класса EchoClient в метод цикла create_connection() результатом является сопрограмма, которую цикл выполняет, пока не завершится.

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

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

Вот результат после подключения двух клиентов:

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

Клиент вызывает функцию open_connection(), которая возвращает объекты чтения и записи, использованные естественным образом. Чтобы закрыть соединение, он закрывает писателя.

Сервер также значительно упрощен.

Работа с подпроцессами

Asyncio также охватывает взаимодействия с подпроцессами. Следующая программа запускает другой процесс Python и выполняет код «import this». Это одно из знаменитых пасхальных яиц Python, на котором напечатано «Zen of Python». Проверьте вывод ниже.

Процесс Python запускается в сопрограмме zen() с помощью функции create_subprocess_exec() и связывает стандартный вывод с каналом. Затем он перебирает стандартные выходные строки построчно, используя await, чтобы дать возможность другим процессам или сопрограммам выполнить их, если вывод еще не готов.

Обратите внимание, что в Windows вы должны установить цикл обработки событий в ProactorEventLoop, потому что стандартный SelectorEventLoop не поддерживает каналы.

Заключение

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

Asyncio в Python - это комплексная среда для асинхронного программирования. Она имеет огромную область применения и поддерживает как низкоуровневые, так и высокоуровневые API. Она все еще относительно молода и не очень хорошо понята сообществом.

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

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.