7 days of WordPress plugins, themes & templates - for free!* Unlimited asset downloads! Start 7-Day Free Trial
Advertisement
  1. Code
  2. Python

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

Read Time: 9 mins

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
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Scroll to top
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.