繁体   English   中英

如何从三个 ReceiveStream 一次读取一行?

[英]How can I read one line at a time from a trio ReceiveStream?

asyncio 有StreamReader.readline() ,允许类似:

while True:
    line = await reader.readline()
    ...

(我在 asyncio 中没有看到async for可用,但这将是明显的演变)

我如何与 trio 实现等效?

我在三重奏 0.9 中没有直接看到对此有任何高级别的支持。 我所看到的只是ReceiveStream.receive_some()它返回任意大小的二进制块; 对我来说,解码并将其转换为逐行的东西似乎并非易事。 是否有我可以使用的标准库函数或代码片段? 我发现 io stdlib 模块看起来很有希望,但我没有看到任何提供“feed”方法的方法。

你是对的,目前 Trio 中没有对此提供高级支持。 应该有一些东西,虽然我不是 100% 确定它应该是什么样子。 我打开了一个问题来讨论它。

同时,您的实现看起来很合理。

如果你想让它更健壮,你可以 (1) 使用bytes bytearray而不是bytes作为缓冲区,以追加和删除分摊 O(n) 而不是 O(n^2),(2) 设置限制在最大行长度上,所以邪恶的同行不能强迫你浪费无限内存缓冲无限长的行,(3)恢复每次调用以find最后一个停止的地方,而不是每次都从头开始,再次避免 O(n^2) 行为。 如果您只处理合理的行长和行为良好的同行,这些都不是非常重要,但它也不会受到伤害。

这是您的代码的调整版本,试图结合这三个想法:

class LineReader:
    def __init__(self, stream, max_line_length=16384):
        self.stream = stream
        self._line_generator = self.generate_lines(max_line_length)

    @staticmethod
    def generate_lines(max_line_length):
        buf = bytearray()
        find_start = 0
        while True:
            newline_idx = buf.find(b'\n', find_start)
            if newline_idx < 0:
                # no b'\n' found in buf
                if len(buf) > max_line_length:
                    raise ValueError("line too long")
                # next time, start the search where this one left off
                find_start = len(buf)
                more_data = yield
            else:
                # b'\n' found in buf so return the line and move up buf
                line = buf[:newline_idx+1]
                # Update the buffer in place, to take advantage of bytearray's
                # optimized delete-from-beginning feature.
                del buf[:newline_idx+1]
                # next time, start the search from the beginning
                find_start = 0
                more_data = yield line

            if more_data is not None:
                buf += bytes(more_data)

    async def readline(self):
        line = next(self._line_generator)
        while line is None:
            more_data = await self.stream.receive_some(1024)
            if not more_data:
                return b''  # this is the EOF indication expected by my caller
            line = self._line_generator.send(more_data)
        return line

(随意使用您喜欢的任何许可证。)

我终于写了这个。 未正确测试(欢迎修复错误),但它似乎有效:

class LineReader:
    def __init__(self, stream):
        self.stream = stream
        self._line_generator = self.generate_lines()

    @staticmethod
    def generate_lines():
        buf = bytes()
        while True:
            newline_idx = buf.find(b'\n')
            if newline_idx < 0:
                # no b'\n' found in buf
                more_data = yield
            else:
                # b'\n' found in buf so return the line and move up buf
                line = buf[:newline_idx+1]
                buf = buf[newline_idx+1:]
                more_data = yield line

            if more_data is not None:
                buf += bytes(more_data)

    async def readline(self):
        line = next(self._line_generator)
        while line is None:
            more_data = await self.stream.receive_some(1024)
            if not more_data:
                return b''  # this is the EOF indication expected by my caller
            line = self._line_generator.send(more_data)
        return line

然后我可以用LineReader包装ReceiveStream并使用它的readline方法。 添加__aiter__()__anext()__将是微不足道的,但在我的情况下我不需要它(我正在移植一些不使用async for东西)。

另一个缺陷是它假定 UTF-8 或类似的编码,其中b'\\n'换行符存在于未修改的编码字节对象中。

不过,依靠库函数来处理这个问题会很好; 其他答案表示赞赏。

我正在使用的一种非常幼稚的方法:

async def readline(stdout: trio.abc.ReceiveStream):
    data = b""
    while True:
        _data = await stdout.receive_some()
        if _data == b"":
            break
        data += _data
        if data.endswith(b"\n"):
            break
    return data

# use it like this:
async def fn():
    async with await trio.open_process(..., stdout=subprocess.PIPE) as process:
        while True:
            # instead of:
            #   data = process.stdout.receive_some()
            # use this:
            line = await readline(process.stdout)
            if line == b"":
                break

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM