И улучшите производительность кода ввода-вывода.

Начиная с Python 3.5, в ваших скриптах можно использовать асинхронизм. Эта эволюция позволила использовать новые ключевые слова async и await вместе с новым модулем asyncio.

Async и Await были впервые представлены в C #, чтобы структурировать неблокирующий код аналогично тому, как вы пишете блокирующий код. Затем эти ключевые слова были экспортированы на несколько языков программирования, поскольку это облегчает управление асинхронным кодом.

Действительно, есть и другие способы написания асинхронного кода, если вы знаете Javascript / Nodejs, значит, вы, вероятно, уже использовали обратные вызовы:

fs = require('fs');
fs.writeFile('hello.txt', 'call back exemple in nodejs', (err) => {
  if (err) return console.log(err);
  console.log('file writen');
});

Функция обратного вызова, переданная здесь во второй позиции, будет вызываться при возникновении события.

Также есть обещания, которые позволяют лучше управлять асинхронными вызовами с помощью выразительных слов:

import axios from 'axios'
let data;
axios.get("https://jsonplaceholder.typicode.com/posts")
  .then(resp => {
                data = resp.data //do data processing as data is actually available
                })
  .catch(err => { console.log(err) })
console.log(data) // undefined even if the instruction is after the API call

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

Проблема со стандартным кодом ввода-вывода в Python.

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

import requests
data = (requests
       .get("https://jsonplaceholder.typicode.com/posts")
       .json()
) # blocking code block, python waiting doing nothing
for d in data: # executed once the API has return
   print(d)

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

def fetch_comments():
  data = []
  for i in range(1, 500):
    url = f"https://jsonplaceholder.typicode.com/comments/{i}"
    resp = requests.get(url).json()
    data.append(resp)
fetch_comments()

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

Классический способ: нити

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

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

С другой стороны, на моей машине выполнение этого кода с 5 порожденными потоками заняло всего 7,05 секунды, что на 94% по сравнению с синхронной версией. Но если я скажу вам, что мы можем добиться большего.

Современный подход: асинхронный код

Давайте посмотрим, как выполнять вызовы API с помощью асинхронного кода.

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

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

Заключение

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

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

Если вы уверены и хотите перейти на асинхронный режим, репозиторий aio-libs предоставляет набор библиотек на основе asyncio, и у них может быть одна, которая соответствует вашим потребностям.