[英]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.