How to stream stdout/stderr from a child process using asyncio, and obtain its exit code after?

前端 未结 3 1450
一向
一向 2020-12-19 15:51

Under Python 3.4 on Windows, I need to stream data written to stdout/stderr by a child process, i.e. receive its output as it occurs, using the asyncio framework introduced

相关标签:
3条回答
  • 2020-12-19 16:26

    Since the event loop may see and notify the process exit before reading the remaining data for stdout/stderr, we need to check PIPE close events in addition to the process exit event.

    This is a correction for aknuds1 answer:

    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()
    
    0 讨论(0)
  • 2020-12-19 16:41

    I guess to use high-level api:

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

    Also you can do more:

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

    and so on.

    But, please, keep in mind that waiting for, say, stdout when child process prints nothing can hang you coroutine.

    0 讨论(0)
  • 2020-12-19 16:48

    The solution I've come up with so far uses SubprocessProtocol to receive output from the child process, and the associated transport to get the process' exit code. I don't know if this is optimal though. I've based my approach on an answer to a similar question by J.F. 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()))
    
    0 讨论(0)
提交回复
热议问题