¿Cómo transmitir stdout / stderr desde un proceso hijo usando asyncio y obtener su código de salida después?

Bajo Python 3.4 en Windows, necesito transmitir datos escritos en stdout / stderr por un proceso secundario, es decir, recibir su salida a medida que se produce, utilizando el marco asyncio introducido en Python 3.4. También tengo que determinar el código de salida del progtwig después. ¿Cómo puedo hacer esto?

La solución que he encontrado hasta ahora utiliza SubprocessProtocol para recibir la salida del proceso hijo, y el transporte asociado para obtener el código de salida del proceso. Aunque no sé si esto es óptimo. He basado mi enfoque en una respuesta a una pregunta similar de JF Sebastian .

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

Como el ciclo de eventos puede ver y notificar la salida del proceso antes de leer los datos restantes para stdout / stderr, debemos verificar los eventos de cierre PIPE además del evento de salida de proceso.

Esta es una corrección para la respuesta de 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() 

Supongo que para usar api de alto nivel :

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

También puedes hacer más:

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

y así.

Pero, por favor, tenga en cuenta que esperar, digamos, la stdout cuando el proceso del niño no se imprime, nada puede colgarle en la rutina.