Search code examples
pythoninputstream

Python StreamIO reading and writing from the same stream


I'm looking for a pythonic means of both reading and writing to a stream (in the IOBase hierarchy) without having to manage the stream position via seek() and tell(). This would be similar to how a socket stream would be expected to work where the receiving end would be able to continuously read while bytes where being received.

I've tried using a BufferedReader and BufferedWriter both attached to the same raw stream, but operations on one affect the stream position on the other. seek()/tell() appear to pass directly through to the underlying raw stream. Are there other IOBase types that can be used to act effectively as an IO stream where concurrent input and output are supported without the need to manage the stream position (similar to C++ stringstream >> and << ).

Thanks!

>>> import io
>>> buf = io.BytesIO()
>>> r = io.BufferedReader(buf)
>>> w = io.BufferedWriter(buf)
>>> w.write('foo bar')
7L
>>> r.read(1)
''
>>> r.tell()
0L
>>> w.flush()
>>> r.tell()
7L
>>> r.flush()
>>> r.tell()
7L
>>> w.tell()
7L
>>> buf.tell()
7L

Solution

  • You cannot directly.

    A socket is anologous to a pair of file descriptors, one used for reading and one for writing. That's the reason why you are allowed to read and write on a a socket without using a seek between different operations.

    If you really want to simulate a socket with StringIO or BytesIO, just build a custom class containing a pair or them.

    It could be something like:

    class BytesSocket(io.IOBase):
        def __init__(self, inputText):
            self.input = io.BytesIO(inputText)
            self.output = io.BytesIO()
        def read(self, n=-1):
            return self.input.read(n)
        def readinto(self, b):
            return self.input.readinto(b)
        def write(self, b):
            return self.output.write(b)
        def getoutvalue(self):
            return self.output.getvalue()
    

    If you need a loopback pseudo socket (read what has been previously written), you could use:

    class BytesLoop(io.IOBase):
        def __init__(self, inputText=''):
            self.buf = inputText
        def read(self, n=-1):
            inp = io.BytesIO(self.buf)
            b = inp.read(n)
            self.buf = self.buf[len(b):]
            return b
        def readinto(self, b):
            inp = io.BytesIO(buf)
            l = inp.readinto(b)
            self.buf = self.buf[l:]
            return l
        def write(self, b):
            outp = io.BytesIO()
            l = outp.write(b)
            self.buf += outp.getvalue()
            return l
        def getvalue(self):
            return self.buf
    

    As a str (or a unicode) is a non mutable sequence, it needs to te re-written for each io operation. You could easily use instead a list of characters which is mutable. You can convert a string to a list with l = [c for c in s ] and the opposite can be done with s = ''.join(l).