如何清除 stringio 对象?

Posted

技术标签:

【中文标题】如何清除 stringio 对象?【英文标题】:how do I clear a stringio object? 【发布时间】:2011-05-18 20:45:49 【问题描述】:

我创建了一个 stringio 对象,其中包含一些文本。我想清除其现有值并重用它而不是召回它。有没有办法做到这一点?

【问题讨论】:

【参考方案1】:

TL;DR

不必费心清除它,只需创建一个新的——它会更快。

方法

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

(感谢tstone2077pointing out the difference。)

>>> 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%,而创建一个新的而不是重用一个 3K 的要快 5%。同样,创建一个新的StringIO,而不是截断和查找。

【讨论】:

为什么你需要先做一个 seek(0) ?似乎将其截断为 size=0 会强制当前文件位置超过新文件大小。 没错,我一直认为它总是从当前位置截断......更新了答案以删除它;这会改变它为StringIO.StringIO,而不是cStringIO.StringIO(看结论) 我赞成这一点,除了对性能的关注是没有根据的,特别是当 OP 明确表示需要清晰和重用时。哦,实际上我还是会投票,因为投票应该意味着“这个答案很有用”,但我仍然认为应该删除“所以你想要截断(0)”之后的所有内容。 @Peter:我认为时代很有用,因为它们证明了(至少使用 cStringIO)我所期望的:创建一个新的 StringIO 比截断现有的 StringIO 更好。 @PeterHansen 我来得很晚,但我非常感谢时间信息。它表明我的假设(也许还有 OP)认为截断会有更好的性能是没有根据的,通常我们最好从新开始。有时,最好的答案表明您问错了问题(尤其是当他们同时提供直接答案时。)【参考方案2】:

有一点需要注意(至少对于 Python 3.2):

seek(0) IS 在 truncate(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'

在截断之前使用 seek(0),我们得到预期的输出:

'111'
''
'111'

【讨论】:

谢谢你;你是绝对正确的。我已经根据这个更新了我的答案;寻找和截断的额外负担进一步巩固了创建新 StringIO 作为明智路径的位置。 不幸的是,我确实需要重用相同的 StringIO 实例,因为我在单元测试中的 sys.stdout 的@patch 修饰符中使用它,你可以' 调用 patch() 时无法访问实例变量,因为尚未创建实例。我在 setup() 函数中添加了 seek(0) 和 truncate(0)。谢谢!【参考方案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()

【讨论】:

是的,这个专门的案例可能会更好。您能否显示基准,使用与我类似的代码(StringIOcStringIO)?我很想看看他们。 @ChrisMorgan 抱歉,我现在才看到你的评论......现在挖掘我的内存/源文件夹为时已晚:) 请注意,在 Python>=3.9 中,reset 方法不存在:'_io.StringIO' object has no attribute 'reset'!

以上是关于如何清除 stringio 对象?的主要内容,如果未能解决你的问题,请参考以下文章

将 io.StringIO 转换为 io.BytesIO

使用 Pandas Excelwriter 写入 StringIO 对象?

python wave模块可以接受StringIO对象吗

有没有办法让 Roo 接受 StringIO 对象代替文件?

使用stringIO对象作为ssl key / cert文件

我们应该使用 pandas.compat.StringIO 还是 Python 2/3 StringIO?