Сообщить об ошибке.

Создание субпроцесса из цикла событий asyncio в Python

Создание подпроцессов низкоуровневым API модуля asyncio

Методы цикла событий, описанные в этом подразделе, относятся к низкоуровневому API. В обычном коде async/await рассмотрите возможность использования вместо них вспомогательных функций высокого уровня asyncio.create_subprocess_shell() и asyncio.create_subprocess_exec().

Примечание. В Windows реализация цикла событий asyncio ProactorEventLoop (по умолчанию) поддерживает подпроцессы, а реализация SelectorEventLoop - нет.

Также в Windows не поддерживается функция политики абстрактного цикла policy.set_child_watcher() (устарело с Python 3.12.), поскольку реализация ProactorEventLoop имеет другой механизм наблюдения за дочерними процессами.

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

Содержание:


loop.subprocess_exec(protocol_factory, *args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs):

Метод loop.subprocess_exec() создает подпроцесс из одного или нескольких строковых аргументов, указанных в args и представляет собой сопрограмму.

Аргумент args должен быть списком строк, представленных как str или bytes, закодированные в кодировке файловой системы.

Первая строка указывает исполняемый файл программы, а остальные строки указывают аргументы. Вместе строковые аргументы образуют sys.argv программы.

Метод loop.subprocess_exec() похож на класс стандартной библиотеки subprocess.Popen, вызываемый с аргументом shell=False и списком строк, переданных в качестве первого аргумента. Если Popen принимает единственный аргумент, который представляет собой список строк, то метод loop.subprocess_exec() принимает несколько строковых аргументов.

Аргумент protocol_factory должен быть вызываемым объектом, возвращающим подкласс класса asyncio.SubprocessProtocol.

Остальные аргументы:

  • stdin может быть любым из следующих:

    • файловый объект, представляющий канал, который должен быть подключен к стандартному входному потоку подпроцесса с помощью loop.connect_write_pipe();
    • константа subprocess.PIPE, которая создаст новый канал и подключит его;
    • значение None, которое заставит подпроцесс наследовать дескриптор файла от этого процесса;
    • константа subprocess.DEVNULL, которая указывает, что будет использоваться специальный файл os.devnull.

  • stdout может быть любым из следующих:

    • файловый объект, представляющий канал, который должен быть подключен к стандартному входному потоку подпроцесса с помощью loop.connect_write_pipe();
    • константа subprocess.PIPE, которая создаст новый канал и подключит его;
    • значение None, которое заставит подпроцесс наследовать дескриптор файла от этого процесса;
    • константа subprocess.DEVNULL, которая указывает, что будет использоваться специальный файл os.devnull.

  • stderr может быть любым из следующих:

    • файловый объект, представляющий канал, который должен быть подключен к стандартному потоку ошибок подпроцесса с помощью loop.connect_write_pipe();
    • константа subprocess.PIPE, которая создаст новый канал и подключит его;
    • значение None, которое заставит подпроцесс наследовать дескриптор файла от этого процесса;
    • константа subprocess.DEVNULL, которая указывает, что будет использоваться специальный файл os.devnull.
    • константа subprocess.STDOUT, которая подключит стандартный поток ошибок к стандартному потоку вывода процесса.

Все остальные ключевые аргументы передаются в subprocess.Popen() без интерпретации, за исключением bufsize, universal_newlines, shell, text, encoding и errors, которые вообще не должны указываться.

API подпроцесса модуля asyncio не поддерживает декодирование потоков как текста. Для преобразования байтов, возвращаемых из потока, в текст может использоваться функция bytes.decode() .

Возвращает пару (transport, protocol), где транспорт соответствует базовому классу asyncio.SubprocessTransport, а протокол - объект, экземпляр которого был создан с помощью аргумента protocol_factory.

loop.subprocess_shell(protocol_factory, cmd, *, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs):

Метод loop.subprocess_shell() создает подпроцесс из cmd, который может быть строкой str или строкой bytes, закодированной в кодировке файловой системы, с использованием синтаксиса терминала платформы.

Представляет собой сопрограмму.

Метод loop.subprocess_shell() похож на класс subprocess.Popen стандартной библиотеки, вызываемый с аргументом shell=True.

Аргумент protocol_factory должен быть вызываемым объектом, возвращающим подкласс класса asyncio.SubprocessProtocol.

Для получения информации об остальных аргументах, смотрите метод цикла событий loop.subprocess_exec().

Возвращает пару (transport, protocol), где транспорт соответствует базовому классу asyncio.SubprocessTransport, а протокол - объект, экземпляр которого был создан с помощью аргумента protocol_factory.

Примечание. Приложение несет ответственность за то, чтобы все пробелы и специальные символы были указаны в кавычках, во избежании уязвимостей. Можно использовать функцию shlex.quote() для правильного экранирования пробелов и специальных символов в строках, которые будут использоваться для создания команд оболочки.

Создание каналов субпроцесса.

В реализации цикла событий SelectorEventLoop канал устанавливается в неблокирующий режим.

Примечание. реализация SelectorEventLoop не поддерживает указанные ниже методы в Windows. Для Windows используйте реализацию ProactorEventLoop.

Смотрите также методы loop.subprocess_exec() и loop.subprocess_shell().

loop.connect_read_pipe(protocol_factory, pipe):

Метод loop.connect_read_pipe() регистрирует один конец канала для чтения в цикле событий. Представляет собой сопрограмму.

  • Аргумент protocol_factory должен быть вызываемым объектом, возвращающим реализацию протокола asyncio.
  • Аргумент pipe - это файлоподобный объект.

Возвращает пару (transport, protocol), где транспорт transport поддерживает интерфейс ReadTransport, а протокол protocol - это объект, созданный с помощью объекта protocol_factory.

В реализации цикла событий SelectorEventLoop канал устанавливается в неблокирующий режим.

loop.connect_write_pipe(protocol_factory, pipe):

Метод loop.connect_write_pipe() регистрирует другой конец канала для записи в цикле событий. Представляет собой сопрограмму.

  • Аргумент protocol_factory должен быть вызываемым объектом, возвращающим реализацию протокола asyncio.
  • Аргумент pipe - это файлоподобный объект.

Возвращает пару (transport, protocol), где транспорт transport поддерживает интерфейс WriteTransport, а протокол protocol - это объект, созданный с помощью объекта protocol_factory.

Транспорт субпроцесса.

asyncio.SubprocessTransport(BaseProtocol):

Класс asyncio.SubprocessTransport(BaseProtocol) представляет собой абстракцию, которая осуществляет связь между родительским и дочерним процессами ОС.

Экземпляры класса SubprocessTransport возвращаются из методов цикла событий loop.subprocess_shell() и loop.subprocess_exec().

Методы транспорта субпроцесса:

SubprocessTransport.get_pid():

Метод SubprocessTransport.get_pid() возвращает идентификатор подпроцесса PID как целое число.

SubprocessTransport.get_pipe_transport(fd):

Метод SubprocessTransport.get_pipe_transport() возвращает транспорт для канала связи, соответствующий целочисленному файловому дескриптору fd:

  • 0: читаемый потоковый транспорт stdin или если подпроцесс был создан без stdin=PIPE - то None;
  • 1: записываемый потоковый транспорт stdout или если подпроцесс был создан без stdout=PIPE - то None;
  • 2: записываемый потоковый транспорт stderr или если подпроцесс был создан без stderr=PIPE - то None;
  • другой дескриптор fd: None.

SubprocessTransport.get_returncode():

Метод SubprocessTransport.get_returncode() возвращает код возврата подпроцесса в виде целого числа или если он не был возвращен - то None.

Метод похож на атрибут subprocess.Popen.returncode]subprocess.Popen.obj.

SubprocessTransport.kill():

Метод SubprocessTransport.kill() убивает подпроцесс.

В системах POSIX функция отправляет SIGKILL подпроцессу. В Windows этот метод является псевдонимом для метода .terminate().

Смотрите также описание метода subprocess.Popen.kill().

SubprocessTransport.send_signal(signal):

Метод SubprocessTransport.send_signal() отправляет номер сигнала в подпроцесс, как это делает subprocess.Popen.send_signal().

SubprocessTransport.terminate():

Метод SubprocessTransport.terminate() останавливает подпроцесс.

В системах POSIX этот метод отправляет SIGTERM в подпроцесс. В Windows, для остановки подпроцесса вызывается функция Windows API TerminateProcess().

Смотрите также описание метода subprocess.Popen.terminate().

SubprocessTransport.close():

Метод SubprocessTransport.close() завершает подпроцесс, вызвав метод SubprocessTransport.kill().

Метод так же закрывает транспорты каналов stdin, stdout и stderr.

Протокол субпроцесса.

asyncio.SubprocessProtocol(BaseProtocol):

Класс asyncio.SubprocessProtocol(BaseProtocol) представляет собой базовый класс для реализации протоколов, взаимодействующих с дочерними процессами (однонаправленные каналы).

Экземпляры протокола дейтаграмм должны создаваться фабриками протоколов, переданными в методы цикла событий loop.subprocess_shell() и loop.subprocess_exec().

Методы протокола субпроцесса:

SubprocessProtocol.pipe_data_received(fd, data):

Метод SubprocessProtocol.pipe_data_received() вызывается, когда дочерний процесс записывает данные в свой stdout или stderr канала.

  • Аргумент fd - это целочисленный файловый дескриптор канала.
  • Аргумент data - это непустой байтовый объект, содержащий полученные данные.

SubprocessProtocol.pipe_connection_lost(fd, exc):

Метод SubprocessProtocol.pipe_connection_lost() вызывается, когда один из каналов, взаимодействующих с дочерним процессом, закрывается.

Аргумент fd - это целочисленный файловый дескриптор, который был закрыт.

SubprocessProtocol.process_exited():

Метод SubprocessProtocol.process_exited() вызывается, когда дочерний процесс выполнился.


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

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

Подпроцесс создается методом цикла событий loop.subprocess_exec():

import asyncio
import sys

class DateProtocol(asyncio.SubprocessProtocol):
    def __init__(self, exit_future):
        self.exit_future = exit_future
        self.output = bytearray()

    def pipe_data_received(self, fd, data):
        self.output.extend(data)

    def process_exited(self):
        self.exit_future.set_result(True)

async def get_date():
    # Получаем ссылку на цикл событий, т.к. 
    # планируем использовать низкоуровневый API.
    loop = asyncio.get_running_loop()

    code = 'import datetime; print(datetime.datetime.now())'
    exit_future = asyncio.Future(loop=loop)

    # Создаем подпроцесс, управляемый 'DateProtocol';
    # перенаправим стандартный вывод в pipe.
    transport, protocol = await loop.subprocess_exec(
        lambda: DateProtocol(exit_future),
        sys.executable, '-c', code,
        stdin=None, stderr=None)

    # Ждем выхода подпроцесса, используя 
    # метод 'DateProtocol.process_exited()' .
    await exit_future

    # Закроем стандартный вывод.
    transport.close()

    # Читаем выходные данные, собранные 
    # методом `DateProtocol.pipe_data_received()`.
    data = bytes(protocol.output)
    return data.decode('ascii').rstrip()

date = asyncio.run(get_date())
print(f"Current date: {date}")