如何在两个Python asyncio协同程序之间使用读/写流?

时间:2017-08-16 10:25:03

标签: python pipe python-asyncio

如何使用asyncio在两个协程之间实现管道,一个从流中读取,另一个从中写入?

假设我们有这个现有的代码,两个简单的脚本。产生于stdout的一个:

# produce.py

import asyncio
import random
import sys

async def produce(stdout):
    for i in range(10000):
        await asyncio.sleep(random.randint(0, 3))
        print(i, file=stdout, flush=True)

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(produce(sys.stdout))
    loop.close()

另一个从stdin读取的内容:

# consume.py

async def consume(loop, stdin):
    reader = asyncio.StreamReader(loop=loop)
    reader_protocol = asyncio.StreamReaderProtocol(reader)
    await loop.connect_read_pipe(lambda: reader_protocol, stdin)

    while True:
        line = await reader.readline()
        if not line:
            break
        print(int(line) ** 2)

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(consume(loop, sys.stdin))
    loop.close()

显然,由于我们的两个部分可以从命令行单独运行,我们可以使用subprocess模块和shell管道(produce | consume)。

但我们希望在Python中实现相当于Unix的管道,即连接这两个现有函数的流。

这样的事情不会起作用:

pipe = io.BytesIO()

await asyncio.gather(produce(pipe),
                     consume(loop, pipe))

如果这两个函数会操作生成器,我们可以编写类似这样的东西(python 3.6):

async def produce():
    for i in range(10000):
        await asyncio.sleep(random.randint(0, 3))
        yield str(i)


async def consume(generator):
    async for value in generator:
        print(int(value) ** 2)


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(consume(produce()))
    loop.close()

是否有asyncio API的某些部分允许这样做?

谢谢!

2 个答案:

答案 0 :(得分:2)

解决这个问题的方法是将当前函数转换为生成器并编写一些包装器以使用Unix管道公开它们:

# wrapper.py

import asyncio
import random
import sys


async def produce():
    for i in range(10000):
        await asyncio.sleep(random.randint(0, 3))
        yield str(i)


async def consume(generator):
    async for value in generator:
        print(int(value) ** 2)


async def system_out_generator(loop, stdout, generator):
    async for line in generator:
        print(line, file=stdout, flush=True)


async def system_in_generator(loop, stdin):
    reader = asyncio.StreamReader(loop=loop)
    reader_protocol = asyncio.StreamReaderProtocol(reader)
    await loop.connect_read_pipe(lambda: reader_protocol, stdin)
    while True:
        line = await reader.readline()
        if not line:
            break
        yield line


async def main(loop):
    try:
        if sys.argv[1] == "produce":
            await system_out_generator(loop, sys.stdout, produce())
        elif sys.argv[1] == "consume":
            await consume(system_in_generator(loop, sys.stdin))
    except IndexError:
        await consume(produce())


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main(loop))

您可以使用:

python wrapper.py  # Python generators

或:

python wrapper.py produce | python wrapper.py consume  # System pipes

答案 1 :(得分:0)

原始帖子说:“类似的东西行不通。”我不确定此语句是否缩进表示“以下代码无效”或“我不希望这种样式的解决方案”。

我会注意到以下代码确实有效:

r, w = os.pipe()
read_pipe = os.fdopen(r, 'r')
write_pipe = os.fdopen(w, 'w')

await asyncio.gather(produce(write_pipe), consume(loop, read_pipe))