说我有以下内容:
dest = "\n".join( [line for line in src.split("\n") if line[:1]!="#"] )
(即从多行字符串src
中删除以#开头的所有行)
src
非常大,所以我假设.split()
会创建一个大的中间列表。我可以将列表理解更改为生成器表达式,但是我可以使用某种“xsplit”一次只能在一行上工作吗?我的假设是否正确?处理这个问题的最有效(内存)方法是什么?
澄清:这是由于我的代码内存不足造成的。我知道有一些方法可以完全重写我的代码来解决这个问题,但问题是关于Python:是否有一个版本的split()(或一个等效的习惯用法),它的行为类似于生成器,因此无法进行额外的工作src
的副本?
答案 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中是不可变的,所以没有中间存储就很难做任何事情。