Перейти к содержимому

18.5.6. Подпроцесс

Исходный код: Lib/asyncio/subprocess.py

18.5.6.1. Цикл событий Windows

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

python
import asyncio, sys

if sys.platform == 'win32':
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)

18.5.6.2. Создание подпроцесса: высокоуровневый API с использованием Process

coroutineasyncio.create_subprocess_exec(*args, stdin=None, stdout=None, stderr=None, loop=None, limit=None, **kwds)

Создать подпроцесс.

Параметр limit задаёт предельный размер буфера, передаваемый в StreamReader. Другие параметры см. в AbstractEventLoop.subprocess_exec().

Возвращает экземпляр Process.

Эта функция является корутиной.

coroutineasyncio.create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None, loop=None, limit=None, **kwds)

Запускает команду оболочки cmd.

Параметр limit задаёт предельный размер буфера, передаваемый в StreamReader. Другие параметры см. в AbstractEventLoop.subprocess_shell().

Возвращает экземпляр Process.

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

Эта функция является корутиной.

Используйте методы AbstractEventLoop.connect_read_pipe() и AbstractEventLoop.connect_write_pipe() для подключения каналов.

18.5.6.3. Создание подпроцесса: низкоуровневый API с использованием subprocess.Popen

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

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

Создаёт подпроцесс из одного или нескольких строковых аргументов (строк символов или байтовых строк, закодированных в кодировку файловой системы), где первая строка задаёт исполняемую программу, а остальные строки – её аргументы. (Таким образом, все строковые аргументы вместе образуют значение sys.argv программы, если предположить, что это скрипт Python.) Это аналогично классу subprocess.Popen стандартной библиотеки, вызванному с shell=False и списком строк, переданным в качестве первого аргумента; однако Popen принимает единственный аргумент – список строк, а subprocess_exec() принимает несколько строковых аргументов.

protocol_factory должен создавать экземпляр подкласса класса asyncio.SubprocessProtocol.

Другие параметры:

  • stdin: либо файлоподобный объект, представляющий канал, подключаемый к стандартному потоку ввода подпроцесса с помощью connect_write_pipe(), либо константа subprocess.PIPE (по умолчанию). По умолчанию создаётся и подключается новый канал.

  • stdout: либо файлоподобный объект, представляющий канал, подключаемый к стандартному потоку вывода подпроцесса с помощью connect_read_pipe(), либо константа subprocess.PIPE (по умолчанию). По умолчанию создаётся и подключается новый канал.

  • stderr: либо файлоподобный объект, представляющий канал, подключаемый к стандартному потоку ошибок подпроцесса с помощью connect_read_pipe(), либо одна из констант subprocess.PIPE (по умолчанию) или subprocess.STDOUT. По умолчанию создаётся и подключается новый канал. Если указана subprocess.STDOUT, стандартный поток ошибок подпроцесса будет подключён к тому же каналу, что и стандартный поток вывода.

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

Возвращает пару (transport, protocol), где транспорт является экземпляром BaseSubprocessTransport.

Этот метод является корутиной.

Параметры см. в конструкторе класса subprocess.Popen.

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

Создаёт подпроцесс из cmd, который является строкой символов или байтовой строкой, закодированной в кодировку файловой системы, с использованием синтаксиса «оболочки» платформы. Это аналогично классу subprocess.Popen стандартной библиотеки, вызванному с shell=True.

protocol_factory должен создавать экземпляр подкласса класса asyncio.SubprocessProtocol.

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

Возвращает пару (transport, protocol), где транспорт является экземпляром BaseSubprocessTransport.

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

Этот метод является корутиной.

18.5.6.4. Константы

asyncio.subprocess.PIPE

Специальное значение, которое можно использовать в качестве аргумента stdin, stdout или stderr для create_subprocess_shell() и create_subprocess_exec() и указывает, что следует открыть канал к стандартному потоку.

asyncio.subprocess.STDOUT

Специальное значение, которое можно использовать в качестве аргумента stderr для create_subprocess_shell() и create_subprocess_exec() и указывает, что стандартный поток ошибок должен направляться в тот же дескриптор, что и стандартный вывод.

asyncio.subprocess.DEVNULL

Специальное значение, которое можно использовать в качестве аргумента stdin, stdout или stderr для create_subprocess_shell() и create_subprocess_exec() и указывает, что будет использован специальный файл os.devnull.

18.5.6.5. Процесс

classasyncio.subprocess.Process

Подпроцесс, созданный функцией create_subprocess_exec() или create_subprocess_shell().

API класса Process был спроектирован близким к API класса subprocess.Popen, но есть некоторые отличия:

  • Нет явного метода poll()

  • Методы communicate() и wait() не принимают параметр timeout: используйте функцию wait_for()

  • Параметр universal_newlines не поддерживается (поддерживаются только строки байтов)

  • Метод wait() класса Process асинхронный, тогда как метод wait() класса Popen реализован как цикл активного ожидания.

Этот класс не потокобезопасен. Смотрите также раздел Подпроцессы и потоки.

coroutinewait()

Ожидает завершения дочернего процесса. Устанавливает и возвращает атрибут returncode.

Этот метод является корутиной.

Примечание

Это приведёт к взаимоблокировке при использовании stdout=PIPE или stderr=PIPE, если дочерний процесс генерирует достаточно вывода в канал, так что он блокируется в ожидании, пока буфер канала ОС примет больше данных. Используйте метод communicate() при работе с каналами, чтобы избежать этого.

coroutinecommunicate(input=None)

Взаимодействие с процессом: отправляет данные в stdin. Читает данные из stdout и stderr до достижения конца файла. Ожидает завершения процесса. Необязательный аргумент input должен содержать данные, отправляемые дочернему процессу, или None, если данные не должны отправляться. Тип input должен быть bytes.

communicate() возвращает кортеж (stdout_data, stderr_data).

Если исключение BrokenPipeError или ConnectionResetError возникает при записи input в stdin, оно игнорируется. Это происходит, когда процесс завершается до записи всех данных в stdin.

Чтобы отправлять данные в stdin процесса, необходимо создать объект процесса с stdin=PIPE. Аналогично, чтобы получить в результирующем кортеже что-то кроме None, нужно также указать stdout=PIPE и/или stderr=PIPE.

Этот метод является корутиной.

Примечание

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

Изменено в версии 3.4.2: Метод теперь игнорирует BrokenPipeError и ConnectionResetError.

send_signal(сигнал)

Отправляет сигнал signal дочернему процессу.

Примечание

В Windows SIGTERM является псевдонимом terminate(). Сигналы CTRL_C_EVENT и CTRL_BREAK_EVENT могут быть отправлены процессам, запущенным с параметром creationflags, который включает CREATE_NEW_PROCESS_GROUP.

terminate()

Останавливает дочерний процесс. В POSIX-системах метод отправляет signal.SIGTERM дочернему процессу. В Windows для остановки дочернего процесса вызывается функция Win32 API TerminateProcess().

kill()

Завершает дочерний процесс. В POSIX-системах функция отправляет SIGKILL дочернему процессу. В Windows kill() является псевдонимом для terminate().

stdin

Стандартный поток ввода (StreamWriter); None, если процесс был создан с stdin=None.

stdout

Стандартный поток вывода (StreamReader); None, если процесс был создан с stdout=None.

stderr

Стандартный поток ошибок (StreamReader); None, если процесс был создан с stderr=None.

Предупреждение

Используйте метод communicate() вместо .stdin.write, .stdout.read или .stderr.read, чтобы избежать взаимоблокировок из-за приостановки чтения или записи потоков и блокировки дочернего процесса.

pid

Идентификатор процесса.

Обратите внимание: для процессов, созданных функцией create_subprocess_shell(), этот атрибут является идентификатором процесса порождённой оболочки.

returncode

Код возврата процесса при его завершении. Значение None указывает, что процесс ещё не завершён.

Отрицательное значение -N указывает, что дочерний процесс был завершён сигналом N (только Unix).

18.5.6.6. Подпроцессы и потоки

asyncio поддерживает запуск подпроцессов из разных потоков, но есть ограничения:

  • Цикл событий должен выполняться в главном потоке

  • Наблюдатель дочерних процессов должен быть создан в главном потоке, до запуска подпроцессов из других потоков. Вызовите функцию get_child_watcher() в главном потоке, чтобы создать наблюдатель.

Класс asyncio.subprocess.Process не является потокобезопасным.

18.5.6.7. Примеры подпроцессов

18.5.6.7.1. Подпроцесс с использованием транспорта и протокола

Пример протокола подпроцесса, используемого для получения вывода подпроцесса и\nожидания его завершения. Подпроцесс создаётся методом\nAbstractEventLoop.subprocess_exec():

python
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)

@asyncio.coroutine
def get_date(loop):
    code = 'import datetime; print(datetime.datetime.now())'
    exit_future = asyncio.Future(loop=loop)

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

    # Ожидать завершения подпроцесса с помощью метода process_exited()
    # протокола
    yield from exit_future

    # Закрыть stdout канал
    transport.close()

    # Прочитать вывод, собранный методом pipe_data_received()
    # метод протокола
    data = bytes(protocol.output)
    return data.decode('ascii').rstrip()

if sys.platform == "win32":
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()

date = loop.run_until_complete(get_date(loop))
print("Current date: %s" % date)
loop.close()

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

Пример использования класса Process для управления\nподпроцессом и класса StreamReader для чтения из стандартного\nвывода. Подпроцесс создаётся функцией\ncreate_subprocess_exec():

python
import asyncio.subprocess
import sys

@asyncio.coroutine
def get_date():
    code = 'import datetime; print(datetime.datetime.now())'

    # Создать подпроцесс, перенаправить стандартный вывод в канал
    create = asyncio.create_subprocess_exec(sys.executable, '-c', code,
                                            stdout=asyncio.subprocess.PIPE)
    proc = yield from create

    # Прочитать одну строку вывода
    data = yield from proc.stdout.readline()
    line = data.decode('ascii').rstrip()

    # Дождаться завершения подпроцесса
    yield from proc.wait()
    return line

if sys.platform == "win32":
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()

date = loop.run_until_complete(get_date())
print("Current date: %s" % date)
loop.close()