asyncio具有StreamReader.readline()
,允许类似:
var data = dataRange.getDisplayValues();
(我看不到while True:
line = await reader.readline()
...
在asyncio中可用,但这将是显而易见的演变)
我如何达到三重奏的效果?
在trio 0.9中,我看不到对此有任何高级支持。我所看到的只是ReceiveStream.receive_some()
,它返回任意大小的二进制块;对我来说,将其解码并转换为逐行代码似乎并不平凡。我可以使用标准的库函数或代码段吗?我发现io stdlib模块看起来很有前途,但是我看不到提供“提要”方法的任何方法。
答案 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