如何清空StringIO对象?

80 投票
3 回答
56570 浏览
提问于 2025-04-16 07:55

我有一个叫做stringio的对象,里面存了一些文本。我想把里面的内容清空,然后继续使用这个对象,而不是重新创建一个。有没有办法做到这一点呢?

3 个回答

3

我优化处理多个文件的方法是:我分块读取文件,处理每一块,然后把处理好的内容写入文件。我使用的是同一个 cStringIO.StringIO 实例,每次用完后都会 reset() 它,然后再写入数据,最后用 truncate() 来截断。这样做的好处是,我只截断当前文件不需要的部分。这似乎让我提高了大约3%的性能。如果有更专业的人能确认一下,这样做是否真的优化了内存分配,那就太好了。

sio = cStringIO.StringIO()
for file in files:
    read_file_chunks_and_write_to_sio(file, sio)
    sio.truncate()
    with open('out.bla', 'w') as f:
        f.write(sio.getvalue())
    sio.reset()
16

有一点很重要需要注意(至少在Python 3.2中):

在使用truncate(0)之前,必须先调用seek(0)。下面是没有使用seek(0)的代码:

from io import StringIO
s = StringIO()
s.write('1'*3)
print(repr(s.getvalue()))
s.truncate(0)
print(repr(s.getvalue()))
s.write('1'*3)
print(repr(s.getvalue()))

运行后输出:

'111'
''
'\x00\x00\x00111'

如果在truncate之前加上seek(0),我们就能得到预期的输出:

'111'
''
'111'
136

总结

别费劲去清空它,直接创建一个新的——这样更快。

方法

Python 2

这是我找出这些事情的方法:

>>> from StringIO import StringIO
>>> dir(StringIO)
['__doc__', '__init__', '__iter__', '__module__', 'close', 'flush', 'getvalue', 'isatty', 'next', 'read', 'readline', 'readlines', 'seek', 'tell', 'truncate', 'write', 'writelines']
>>> help(StringIO.truncate)
Help on method truncate in module StringIO:

truncate(self, size=None) unbound StringIO.StringIO method
    Truncate the file's size.

    If the optional size argument is present, the file is truncated to
    (at most) that size. The size defaults to the current position.
    The current file position is not changed unless the position
    is beyond the new file size.

    If the specified size exceeds the file's current size, the
    file remains unchanged.

所以,你想用 .truncate(0)。不过,初始化一个新的 StringIO 可能更便宜(也更简单)。下面有基准测试的结果。

Python 3

(感谢 tstone2077 提醒我 这两者之间的区别。)

>>> from io import StringIO
>>> dir(StringIO)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush', 'getvalue', 'isatty', 'line_buffering', 'newlines', 'read', 'readable', 'readline', 'readlines', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 'writelines']
>>> help(StringIO.truncate)
Help on method_descriptor:

truncate(...)
    Truncate size to pos.

    The pos argument defaults to the current file position, as
    returned by tell().  The current file position is unchanged.
    Returns the new absolute position.

需要注意的是,现在 当前文件位置没有改变,而在 Python 2 中,清空到零大小会重置位置。

因此,对于 Python 2,你只需要:

>>> from cStringIO import StringIO
>>> s = StringIO()
>>> s.write('foo')
>>> s.getvalue()
'foo'
>>> s.truncate(0)
>>> s.getvalue()
''
>>> s.write('bar')
>>> s.getvalue()
'bar'

如果你在 Python 3 中这样做,你可能得不到预期的结果:

>>> from io import StringIO
>>> s = StringIO()
>>> s.write('foo')
3
>>> s.getvalue()
'foo'
>>> s.truncate(0)
0
>>> s.getvalue()
''
>>> s.write('bar')
3
>>> s.getvalue()
'\x00\x00\x00bar'

所以在 Python 3 中,你还需要重置位置:

>>> from cStringIO import StringIO
>>> s = StringIO()
>>> s.write('foo')
3
>>> s.getvalue()
'foo'
>>> s.truncate(0)
0
>>> s.seek(0)
0
>>> s.getvalue()
''
>>> s.write('bar')
3
>>> s.getvalue()
'bar'

如果在 Python 2 的代码中使用 truncate 方法,最好同时调用 seek(0)(前后都可以),这样在你不可避免地迁移到 Python 3 时,代码就不会出错。而且还有另一个理由,为什么你应该直接创建一个新的 StringIO 对象!

时间

Python 2

>>> from timeit import timeit
>>> def truncate(sio):
...     sio.truncate(0)
...     return sio
... 
>>> def new(sio):
...     return StringIO()
... 

当为空时,使用 StringIO:

>>> from StringIO import StringIO
>>> timeit(lambda: truncate(StringIO()))
3.5194039344787598
>>> timeit(lambda: new(StringIO()))
3.6533868312835693

当有 3KB 数据时,使用 StringIO:

>>> timeit(lambda: truncate(StringIO('abc' * 1000)))
4.3437709808349609
>>> timeit(lambda: new(StringIO('abc' * 1000)))
4.7179079055786133

使用 cStringIO 时也是一样:

>>> from cStringIO import StringIO
>>> timeit(lambda: truncate(StringIO()))
0.55461597442626953
>>> timeit(lambda: new(StringIO()))
0.51241087913513184
>>> timeit(lambda: truncate(StringIO('abc' * 1000)))
1.0958449840545654
>>> timeit(lambda: new(StringIO('abc' * 1000)))
0.98760509490966797

所以,忽略潜在的内存问题(del oldstringio),清空一个 StringIO.StringIO 的速度更快(空时快 3%,3KB 数据时快 8%),但创建一个新的 cStringIO.StringIO 更快(空时快 8%,3KB 数据时快 10%)。所以我建议直接使用最简单的方法——假设你在用 CPython,使用 cStringIO 并创建新的对象。

Python 3

相同的代码,只是加上了 seek(0)

>>> def truncate(sio):
...     sio.truncate(0)
...     sio.seek(0)
...     return sio
... 
>>> def new(sio):
...     return StringIO()
...

当为空时:

>>> from io import StringIO
>>> timeit(lambda: truncate(StringIO()))
0.9706327870007954
>>> timeit(lambda: new(StringIO()))
0.8734330690022034

当有 3KB 数据时:

>>> timeit(lambda: truncate(StringIO('abc' * 1000)))
3.5271066290006274
>>> timeit(lambda: new(StringIO('abc' * 1000)))
3.3496507499985455

所以对于 Python 3,创建一个新的对象比重用一个空的快 11%,而创建一个新的对象比重用一个 3KB 的快 5%。再次强调,创建一个新的 StringIO 比清空和重置位置要更好。

撰写回答