Search code examples
pythonpython-2.7python-internalsstringiocstringio

in-place replacement in StringIO


How do I replace a string with another inside a StringIO? - I've heard it's possible if they're the same length.

Attempt:

from cStringIO import StringIO

c = 'can\nhaz\nfoo'

sio = StringIO(c)

for line in sio:
    if line == 'haz\n':
        # sio.write('bar\n')
        line = 'bar\n'
        break

sio.seek(0)
sio.readlines()   # [ 'can\n', 'haz\n', 'bar' ]

PS: Currently working on a solution in C, but would much rather make this work.


Solution

  • StringIO tries to mock up a regular file in read/write, except that it's stored in memory. You have the same issues when trying to edit a text file in-place than with a regular file (replacing by longer string overwrites the following line). You also have the same options.

    Small other change: cStringIO created with a default string at start is not writable. But you can workaround this by creating an empty object and write to it (you obviously have access to a write method now)

    What you're looking for:

    c = 'can\nhaz\nfoo'
    
    sio = StringIO()
    sio.write(c)
    sio.seek(0)
    
    offset = sio.tell()
    for line in sio:
        if line == 'haz\n':
            sio.seek(offset)
            sio.write('bar\n')
            break
        offset = sio.tell()
    

    It stores the offset before the lineread and if pattern is found, it seeks back to the previous offset, and writes the same number of bytes

    Brutal solution if you want to use cStringIO: you can read the buffer fully, perform your replace, and write back to it (allows replacing by longer strings), but reads the whole buffer, which is probably not what you want:

    c = 'can\nhaz\nfoo'
    
    sio = StringIO(c)
    
    v = sio.getvalue()
    v = v.replace("haz","bar")
    sio.seek(0)
    sio.write(v)
    

    Note that Python 3 removed those objects, now it's just io. So relying on such or such implementation will make your code non-portable.