如何在不在Python中创建中间列表的情况下拆分字符串并重新加入?

时间:2010-08-23 07:52:11

标签: python string generator iterator

说我有以下内容:

dest = "\n".join( [line for line in src.split("\n") if line[:1]!="#"] )

(即从多行字符串src中删除以#开头的所有行)

src非常大,所以我假设.split()会创建一个大的中间列表。我可以将列表理解更改为生成器表达式,但是我可以使用某种“xsplit”一次只能在一行上工作吗?我的假设是否正确?处理这个问题的最有效(内存)方法是什么?

澄清:这是由于我的代码内存不足造成的。我知道有一些方法可以完全重写我的代码来解决这个问题,但问题是关于Python:是否有一个版本的split()(或一个等效的习惯用法),它的行为类似于生成器,因此无法进行额外的工作src的副本?

5 个答案:

答案 0 :(得分:5)

buffer = StringIO(src)
dest = "".join(line for line in buffer if line[:1]!="#")

当然,如果您始终使用StringIO,这确实最有意义。它的工作方式与文件大致相同。您可以搜索,读取,写入,迭代(如图所示)等。

答案 1 :(得分:5)

这是使用itertools

进行常规分割的方法
>>> import itertools as it
>>> src="hello\n#foo\n#bar\n#baz\nworld\n"
>>> line_gen = (''.join(j) for i,j in it.groupby(src, "\n".__ne__) if i)
>>> '\n'.join(s for s in line_gen if s[0]!="#")
'hello\nworld'

groupby分别处理src中的每个char,因此性能可能不是很好,但它确实避免了创建任何中间庞大的数据结构

花费几行并制作发电机可能更好

>>> src="hello\n#foo\n#bar\n#baz\nworld\n"
>>>
>>> def isplit(s, t): # iterator to split string s at character t
...     i=j=0
...     while True:
...         try:
...             j = s.index(t, i)
...         except ValueError:
...             if i<len(s):
...                 yield s[i:]
...             raise StopIteration
...         yield s[i:j]
...         i = j+1
...
>>> '\n'.join(x for x in isplit(src, '\n') if x[0]!='#')
'hello\nworld'

re有一个名为finditer的方法,也可以用于此目的

>>> import re
>>> src="hello\n#foo\n#bar\n#baz\nworld\n"
>>> line_gen = (m.group(1) for m in re.finditer("(.*?)(\n|$)",src))
>>> '\n'.join(s for s in line_gen if not s.startswith("#"))
'hello\nworld'

比较性能是OP尝试实际数据的练习

答案 2 :(得分:4)

在现有代码中,您可以将列表更改为生成器表达式:

dest = "\n".join(line for line in src.split("\n") if line[:1]!="#")

这个非常小的更改可以避免在代码中构建两个临时列表中的一个,并且不需要您付出任何努力。

避免临时构建两个列表的完全不同的方法是使用正则表达式:

import re
regex = re.compile('^#.*\n?', re.M)
dest = regex.sub('', src)

这不仅可以避免创建临时列表,还可以避免为输入中的每一行创建临时字符串。以下是建议解决方案的一些性能测量:

init = r'''
import re, StringIO
regex = re.compile('^#.*\n?', re.M)
src = ''.join('foo bar baz\n' for _ in range(100000))
'''

method1 = r'"\n".join([line for line in src.split("\n") if line[:1] != "#"])'
method2 = r'"\n".join(line for line in src.split("\n") if line[:1] != "#")'
method3 = 'regex.sub("", src)'
method4 = '''
buffer = StringIO.StringIO(src)
dest = "".join(line for line in buffer if line[:1] != "#")
'''

import timeit

for method in [method1, method2, method3, method4]:
    print timeit.timeit(method, init, number = 100)

结果:

 9.38s   # Split then join with temporary list
 9.92s   # Split then join with generator
 8.60s   # Regular expression
64.56s   # StringIO

正如您所看到的,正则表达式是最快的方法。

根据您的评论,我可以看到您实际上对避免创建临时对象感兴趣。您真正想要的是减少程序的内存需求。临时对象不一定会影响程序的内存消耗,因为Python很适合快速清理内存。问题来自于在内存中存在的对象比他们需要的时间更长,并且所有这些方法都存在这个问题。

如果你的内存不足,我建议你不应该完全在内存中执行此操作。而是将输入和输出存储在磁盘上的文件中,并以流式方式从中读取。这意味着你从输入中读取一行,在输出中写一行,读一行,写一行等。这将创建大量的临时字符串,但即便如此,它只需要几乎没有内存,因为你只需要处理一次一个字符串。

答案 3 :(得分:2)

如果我理解您关于“更通用的split()调用”的问题,可以使用re.finditer,如下所示:

output = ""

for i in re.finditer("^.*\n",input,re.M):
    i=i.group(0).strip()
    if i.startswith("#"):
        continue
    output += i + "\n"

在这里,你可以用更复杂的东西替换正则表达式。

答案 4 :(得分:1)

问题是字符串在python中是不可变的,所以没有中间存储就很难做任何事情。