Как передать поток stdout/stderr из дочернего процесса с помощью asyncio и получить его код выхода после?

В Python 3.4 в Windows мне нужно передавать данные, записанные в stdout/stderr дочерним процессом, т. е. получать его вывод по мере его возникновения, используя asyncio, представленный в Python 3.4. Я также должен определить код выхода программы после этого. Как я могу это сделать?


person aknuds1    schedule 26.06.2014    source источник


Ответы (3)


Решение, которое я придумал до сих пор, использует SubprocessProtocol для получения вывод дочернего процесса и связанный с ним транспорт для получения кода завершения процесса. Хотя не знаю, оптимально ли это. Я основывал свой подход на ответе на аналогичный вопрос Дж. Ф. Себастьяна.

import asyncio
import contextlib
import os
import locale


class SubprocessProtocol(asyncio.SubprocessProtocol):
    def pipe_data_received(self, fd, data):
        if fd == 1:
            name = 'stdout'
        elif fd == 2:
            name = 'stderr'
        text = data.decode(locale.getpreferredencoding(False))
        print('Received from {}: {}'.format(name, text.strip()))

    def process_exited(self):
        loop.stop()


if os.name == 'nt':
    # On Windows, the ProactorEventLoop is necessary to listen on pipes
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()
with contextlib.closing(loop):
    # This will only connect to the process
    transport = loop.run_until_complete(loop.subprocess_exec(
        SubprocessProtocol, 'python', '-c', 'print(\'Hello async world!\')'))[0]
    # Wait until process has finished
    loop.run_forever()
    print('Program exited with: {}'.format(transport.get_returncode()))
person aknuds1    schedule 26.06.2014

Поскольку цикл обработки событий может видеть и уведомлять о завершении процесса до чтения оставшихся данных для stdout/stderr, нам необходимо проверять события закрытия PIPE в дополнение к событию выхода из процесса.

Это исправление для ответа aknuds1:

class SubprocessProtocol(asyncio.SubprocessProtocol):
    def __init__(self):
        self._exited = False
        self._closed_stdout = False
        self._closed_stderr = False

    @property
    def finished(self):
        return self._exited and self._closed_stdout and self._closed_stderr

    def signal_exit(self):
        if not self.finished:
            return
        loop.stop()        

    def pipe_data_received(self, fd, data):
        if fd == 1:
            name = 'stdout'
        elif fd == 2:
            name = 'stderr'
        text = data.decode(locale.getpreferredencoding(False))
        print('Received from {}: {}'.format(name, text.strip()))

    def pipe_connection_lost(self, fd, exc):
        if fd == 1:
            self._closed_stdout = True
        elif fd == 2:
            self._closed_stderr = True
        self.signal_exit()

    def process_exited(self):
        self._exited = True
        self.signal_exit()
person ymmt2005    schedule 19.05.2015

Я предполагаю использовать API высокого уровня:

proc = yield from asyncio.create_subprocess_exec(
    'python', '-c', 'print(\'Hello async world!\')')

stdout, stderr = yield from proc.communicate()

retcode = proc.returncode

Также вы можете сделать больше:

yield from proc.stdin.write(b'data')
yield from proc.stdin.drain()

stdout = yield from proc.stdout.read()
stderr = yield from proc.stderr.read()

retcode = yield from proc.wait()

и так далее.

Но, пожалуйста, имейте в виду, что ожидание, скажем, stdout, когда дочерний процесс ничего не напечатает, может повесить вашу сопрограмму.

person Andrew Svetlov    schedule 26.06.2014
comment
Однако ни один из этих методов не блокирует? Мне нужно обрабатывать вывод как на stdout, так и на stderr по мере их поступления, а не после выхода дочернего процесса. - person aknuds1; 27.06.2014
comment
Может ли второй пример зайти в тупик, если дочерний процесс генерирует достаточно вывода на stderr? - person jfs; 26.12.2014
comment
Да, оно может. Вы можете создать отдельные сопрограммы чтения для stdout и stderr и выполнять их параллельно, например, с помощью asyncio.gather(). - person Andrew Svetlov; 26.12.2014