何时使用StringIO,而不是连接字符串列表?

55

使用StringIO作为字符串缓冲区比使用列表作为缓冲区慢。

什么时候使用StringIO?

from io import StringIO


def meth1(string):
    a = []
    for i in range(100):
        a.append(string)
    return ''.join(a)

def meth2(string):
    a = StringIO()
    for i in range(100):
        a.write(string)
    return a.getvalue()


if __name__ == '__main__':
    from timeit import Timer
    string = "This is test string"
    print(Timer("meth1(string)", "from __main__ import meth1, string").timeit())
    print(Timer("meth2(string)", "from __main__ import meth2, string").timeit())

结果:

16.7872819901
18.7160351276

1
你可能是想说上面的是"When"而不是"Where"吧? - Lennart Regebro
使用列表推导式尝试meth1。 - Barney Szabolcs
4个回答

35

StringIO的主要优势是可以用作文件预期的位置。因此,您可以执行例如以下操作(对于Python 2):

import sys
import StringIO

out = StringIO.StringIO()
sys.stdout = out
print "hi, I'm going out"
sys.stdout = sys.__stdout__
print out.getvalue()

它能在Python 2中与“with”一起使用吗?从我在这里看到的,似乎不行:http://bugs.python.org/issue1286 - Mr_and_Mrs_D
@Mr_and_Mrs_D 请查看[http://bugs.python.org/issue1286#msg176512],其中说明它将在2.5及以上版本中运行。你还想要什么,要我用血签名吗? :D - Mark Lawrence
@MarkLawrence:不,它不会 - 重新阅读您链接的评论 - 您必须自己编写上下文管理器。 - Mr_and_Mrs_D

27
如果要测量速度,应该使用 cStringIO 。
根据文档
块引用:

模块cStringIO提供了与StringIO模块类似的接口。可以使用此模块中的函数StringIO()更有效地使用大量StringIO.StringIO对象。 但是,StringIO的重点是成为“类文件对象”,当某些东西期望这样做并且您不想使用实际文件时,就会出现这种情况。

编辑:我注意到您使用 from io import StringIO ,因此您可能正在使用Python> = 3或至少2.6。单独的StringIO和cStringIO在Py3中已经消失了。不确定他们用来提供io.StringIO的实现。也有 io.BytesIO 。

使用 cStringIO 来尝试。结果:列表:17,cString:33。 - user225312
3
如果你的平台上存在C实现,io.StringIO会使用它。否则,它将使用Python实现的备选方案。它比较慢的原因是因为它在第一步就没有必要使用StringIO来完成他正在做的事情。 - Lennart Regebro
1
Python 3中已经移除cStringIO模块。 - Jeyekomon

18

我不确定是否应该将这称为“缓冲”,因为你只是用两种复杂的方式将字符串乘以100次。下面是一种简单的方法:

def meth3(string):
    return string * 100
如果我们将这个加入到你的测试中:
if __name__ == '__main__':

    from timeit import Timer
    string = "This is test string"
    # Make sure it all does the same:
    assert(meth1(string) == meth3(string))
    assert(meth2(string) == meth3(string))
    print(Timer("meth1(string)", "from __main__ import meth1, string").timeit())
    print(Timer("meth2(string)", "from __main__ import meth2, string").timeit())
    print(Timer("meth3(string)", "from __main__ import meth3, string").timeit())

结果还有一个额外的好处:它运行速度更快。

21.0300650597
22.4869811535
0.811429977417
如果你想创建一堆字符串,然后将它们连接起来,那么使用meth1()是正确的方法。没有必要将它写入StringIO中,因为它完全是另一种东西,即具有类似于文件流接口的字符串。

-1

另一种基于Lennart Regebro方法的方法。 这比列表方法(meth1)更快。

def meth4(string):
    a = StringIO(string * 100)
    contents = a.getvalue()
    a.close()
    return contents

if __name__ == '__main__':
    from timeit import Timer
    string = "This is test string"
    print(Timer("meth1(string)", "from __main__ import meth1, string").timeit())
    print(Timer("meth2(string)", "from __main__ import meth2, string").timeit())
    print(Timer("meth3(string)", "from __main__ import meth3, string").timeit())
    print(Timer("meth4(string)", "from __main__ import meth4, string").timeit())

结果(秒):

方法1 = 7.731315963647944

方法2 = 9.609279402186985

方法3 = 0.26534052061106195

方法4 = 2.915035489152274


1
我认为将字符串包装在StringIO中,然后立即将其转换回字符串并丢弃StringIO对象是没有任何用处的,特别是如果您关心运行时。 - Nathan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接