python 3:使用readahead从stdin管道读取字节

时间:2013-01-11 17:01:31

标签: python python-3.x

我想读取字节。 sys.stdin在textmode中打开,但它有一个可用于读取字节的缓冲区: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从stdin的缓冲区中获取一些字节,将其切换为适当的数字,然后祈祷,因为peek并不保证任何内容:它可能会给出少于或多于你要求......

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

    peek实际上是未记录的,并且为您提供了大量字节,您必须对性能密切切片。

  3. 顺便说一句。该错误实际上仅适用于管道:对于./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)
    

    如果您忘记了这个问题:如何在没有de /编码任何内容的情况下从stdin获取下一个字节,并且不推进流的光标?

3 个答案:

答案 0 :(得分:6)

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

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

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)

在支持完整sys.stdin的Python 3中,同时查看未解码的流很复杂 - 如上所示将包装stdin.buffer,然后在可窥探的流上实例化新的TextIOWrapper,并安装TextIOWrappersys.stdin

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

答案 1 :(得分:3)

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

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

user4815162342的解决方案将返回每个连续查看呼叫的顺序数据块。这意味着如果用户希望多次使用相同的数据,则必须再次包装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

可以应用其他优化措施,例如在耗尽缓冲区的读取调用时移除先前缓冲的数据。当前实现在缓冲区中留下任何偷看的数据,但该数据无法访问。

答案 2 :(得分:0)

尝试一下:

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'