【问题标题】:python 3: reading bytes from stdin pipe with readaheadpython 3:使用预读从标准输入管道读取字节
【发布时间】:2013-01-11 17:01:31
【问题描述】:

我想读取字节。 sys.stdin 以文本模式打开,但它有一个可用于读取字节的缓冲区:sys.stdin.buffer

我的问题是,当我将数据通过管道传输到 python 时,如果我想要预读,我似乎只有 2 个选项,否则我会得到一个 io.UnsupportedOperation: File or stream is not seekable.

  1. sys.stdin 读取缓冲文本,将该文本解码为字节,然后返回

    (sys.stdin.read(1).decode(); sys.stdin.seek(-1, io.SEEK_CUR).

    由于输入流中的字节不可编码而无法接受。

  2. 使用peek 从标准输入的缓冲区中获取一些字节,将其分割成适当的数字,然后祈祷,因为peek 并不能保证任何事情:它可能提供的比您要求的要少或多……

    (sys.stdin.buffer.peek(1)[:1])

    peek 的文档确实不足,并为您提供了一堆字节,您必须对这些字节进行性能密集型切片。

顺便说一句。该错误仅在管道时适用:对于./myscript.py <somefilesys.stdin.buffer 支持查找。然而sys.stdin 始终是相同的对象层次结构:

$ cat testio.py
#!/usr/bin/env python3
from sys import stdin
print(stdin)
print(stdin.buffer)
print(stdin.buffer.raw)"
$ ./testio.py
<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>
<_io.BufferedReader name='<stdin>'>
<_io.FileIO name='<stdin>' mode='rb'>
$ ./testio.py <somefile
[the same as above]
$ echo hi | ./testio.py
[the same as above]

一些最初的想法,例如将字节流包装到随机访问缓冲区中,并出现与上述相同的错误:BufferedRandom(sys.stdin.buffer).seek(0)io.UnsupportedOperation…

最后,为了您的方便,我介绍一下:

Python 的 io 类层次结构

IOBase
├RawIOBase
│└FileIO
├BufferedIOBase  (buffers a RawIOBase)
│├BufferedWriter┐ 
│├BufferedReader│
││        └─────┴BufferedRWPair
│├BufferedRandom (implements seeking)
│└BytesIO        (wraps a bytes)
└TextIOBase
 ├TextIOWrapper  (wraps a BufferedIOBase)
 └TextIO         (wraps a str)

如果您忘记了这个问题:如何在不解码/编码任何内容且不前进流的光标的情况下从标准输入获取下一个字节?

【问题讨论】:

  • 我提交了python issue 16937,因为我被引导相信您可以使用-u 来禁用stdin 上的缓冲。到目前为止,您还没有答案。
  • 你知道让 peek 正常工作的标准输入缓冲区大小吗?

标签: python python-3.x


【解决方案1】:

异常不是来自 Python,而是来自操作系统,它不允许在管道上搜索。 (如果您从常规管道重定向输出,即使它是标准输入,也可以查找它。)这就是为什么您在一种情况下得到错误而不是在另一种情况下得到错误,即使类是相同的。

用于预读的经典 Python 2 解决方案是将流包装在您自己的实现预读的流实现中:

class Peeker(object):
    def __init__(self, fileobj):
        self.fileobj = fileobj
        self.buf = cStringIO.StringIO()

    def _append_to_buf(self, contents):
        oldpos = self.buf.tell()
        self.buf.seek(0, os.SEEK_END)
        self.buf.write(contents)
        self.buf.seek(oldpos)

    def peek(self, size):
        contents = self.fileobj.read(size)
        self._append_to_buf(contents)
        return contents

    def read(self, size=None):
        if size is None:
            return self.buf.read() + self.fileobj.read()
        contents = self.buf.read(size)
        if len(contents) < size:
            contents += self.fileobj.read(size - len(contents))
        return contents

    def readline(self):
        line = self.buf.readline()
        if not line.endswith('\n'):
            line += self.fileobj.readline()
        return line

sys.stdin = Peeker(sys.stdin)

在 Python 3 中支持完整的sys.stdin,同时窥视未解码的流很复杂——如上所示,将stdin.buffer 包装起来,然后在可窥视的流上实例化一个新的TextIOWrapper,并将TextIOWrapper 安装为@ 987654326@.

但是,由于您只需要查看sys.stdin.buffer,因此将cStringIO.StringIO 更改为io.BytesIO 并将'\n' 更改为b'\n' 后,上述代码将正常工作。

【讨论】:

  • 感谢您的解释,尽管我不得不解释说我确实正确地猜到了管道不可搜索,并且知道我需要以某种方式包装stdin.buffer。我认为有比实现我自己的类更好的方法来做到这一点,但你的回答似乎暗示没有办法说服大量的内置 io 类来做到这一点。然而,我仍然没有得到的是为什么如果我想要字节,我需要在 TextIOWrapper 中包装一个可窥视的流。此外,正如我在问题中所说,我已经可以致电stdin.buffer.peek,所以我不明白您为什么要显示该代码……:/
  • 你在那个地方需要字节,但如果我理解正确的话,程序的其余部分也需要一个有效的sys.stdin。发布的 Python 2 代码的要点是,它使用 StringIO 实现了可靠的 peek,因为正如您所指出的,BufferedReader.peek 可以返回比请求更少或更多的字节,并且您只能调用它一次。答案中的类没有这些限制。
  • 啊,现在它点击了。好的!我不需要“工作”的标准输入,因为该程序不是交互式的,只是解析通过标准输入输入的字节。但是,您也为此提供了解决方案,这真是太棒了。真正的答案(python3)只是你的s/cStringIO.StringIO/io.BytesIO/s/'\n'/b\0/,我说得对吗?
  • 当然,如果您不需要 stdin 来引用您的底层 peekable 流,那么您只需要一个包装在 stdin.buffer 上,如上所示,然后将其移植到 Python 3 应该就像将StringIO 更改为BytesIO'\n' 更改为b'\n' 一样简单。请注意,包装器仅支持readreadline——您可能还想添加__iter__
【解决方案2】:

user4815162342 的解决方案虽然非常有用,但似乎存在一个问题,即它与 io.BufferedReader peek 方法的当前行为不同。

内置方法将为连续 peek() 调用返回相同的数据(从当前读取位置开始)。

user4815162342 的解决方案将为每个连续的 peek 调用返回连续的数据块。这意味着如果用户希望多次使用相同的数据,则必须再次包装 peek 以连接输出。

这是返回内置行为的修复:

def _buffered(self):
    oldpos = self.buf.tell()
    data = self.buf.read()
    self.buf.seek(oldpos)
    return data

def peek(self, size):
    buf = self._buffered()[:size]
    if len(buf) < size:
        contents = self.fileobj.read(size - len(buf))
        self._append_to_buf(contents)
        return self._buffered()
    return buf

See the full version here

还有其他可以应用的优化,例如在耗尽缓冲区的读取调用时删除先前缓冲的数据。当前的实现将任何偷看的数据留在缓冲区中,但这些数据是不可访问的。

【讨论】:

    【解决方案3】:

    试试这个:

    import sys
    
    ssb = sys.stdin.buffer.read(1)
    if ssb == b'h':
        print(ssb+sys.stdin.buffer.read())
    

    回显一个字符串:

    a@fuhq:~$ echo 'hi' | python3 buf_test.py 
    b'hi\n'
    

    重定向文件:

    a@fuhq:~$ cat hi.text
    hi
    a@fuhq:~$ python3 buf_test.py   <  hi.text
    b'hi\n'
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-04
      • 2019-05-01
      • 2015-10-08
      • 2015-07-05
      • 2019-01-06
      • 2020-05-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多