如何一次从三人组ReceiveStream读取一行?

时间:2018-12-01 23:29:02

标签: python python-trio

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

var data = dataRange.getDisplayValues();

(我看不到while True: line = await reader.readline() ... 在asyncio中可用,但这将是显而易见的演变)

我如何达到三重奏的效果?

在trio 0.9中,我看不到对此有任何高级支持。我所看到的只是ReceiveStream.receive_some(),它返回任意大小的二进制块;对我来说,将其解码并转换为逐行代码似乎并不平凡。我可以使用标准的库函数或代码段吗?我发现io stdlib模块看起来很有前途,但是我看不到提供“提要”方法的任何方法。

3 个答案:

答案 0 :(得分:5)

是的,目前Trio中没有对此的高级支持。应该有一些东西,尽管我不是100%地确定它的外观。我打开an issue进行了讨论。

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

如果要使其更加健壮,可以(1)使用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

(可以随意使用任何许可证。)

答案 1 :(得分:0)

我最终写了这篇。未经正确测试(欢迎使用错误修正),但似乎可以正常工作:

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

然后,我可以用ReceiveStream包装LineReader并使用其readline方法。这样,添加__aiter__()__anext()__就很简单了,但在我的情况下,我并不需要它(我正在将某些东西移植到三人中,这些东西无论如何都不会使用async for)。

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

不过最好还是依靠库函数来处理这个问题。其他答案表示赞赏。

答案 2 :(得分:0)

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

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