Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 208
Stanton Avatar asked Oct 28 '15 15:10

Stanton


2 Answers

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).

like image 135
Serge Ballesta Avatar answered Oct 11 '22 06:10

Serge Ballesta


The following straightforward string example runs a fair amount faster than Serge's second example above (at least if the data held in the stream is relatively small) and works for simple read and writes to the same stream:

class BytesLoop:
    def __init__(self, s=b''):
        self.buffer = s

    def read(self, n=-1):
        chunk = self.buffer[:n]
        self.buffer = self.buffer[n:]
        return chunk

    def write(self, s):
        self.buffer += s
like image 20
Matthew D. Scholefield Avatar answered Oct 11 '22 07:10

Matthew D. Scholefield