遍历一个string的行

我有一个像这样定义的多行string:

foo = """ this is a multi-line string. """ 

这个string我们用作我正在编写的parsing器的testinginput。 parsing器函数接收一个file作为input并对其进行迭代。 它也直接调用next()方法来跳过行,所以我确实需要一个迭代器作为input,而不是一个迭代器。 我需要一个迭代器迭代该string的各行,如file将超过文本文件的行。 我当然可以这样做:

 lineiterator = iter(foo.splitlines()) 

有没有更直接的方法呢? 在这种情况下,string必须遍历一次,然后再由parsing器遍历。 在我的testing中,这并不重要,因为string很短,我只是出于好奇而问。 Python有这么多有用的和有效的内置的东西,但我找不到适合这种需求的东西。

这里有三种可能性:

 foo = """ this is a multi-line string. """ def f1(foo=foo): return iter(foo.splitlines()) def f2(foo=foo): retval = '' for char in foo: retval += char if not char == '\n' else '' if char == '\n': yield retval retval = '' if retval: yield retval def f3(foo=foo): prevnl = -1 while True: nextnl = foo.find('\n', prevnl + 1) if nextnl < 0: break yield foo[prevnl + 1:nextnl] prevnl = nextnl if __name__ == '__main__': for f in f1, f2, f3: print list(f()) 

作为主要脚本运行这个确认这三个函数是等价的。 随着timeit (和一个* 100foo得到大量的string更精确的测量):

 $ python -mtimeit -s'import asp' 'list(asp.f3())' 1000 loops, best of 3: 370 usec per loop $ python -mtimeit -s'import asp' 'list(asp.f2())' 1000 loops, best of 3: 1.36 msec per loop $ python -mtimeit -s'import asp' 'list(asp.f1())' 10000 loops, best of 3: 61.5 usec per loop 

注意,我们需要list()调用来确保遍历迭代器,而不是仅仅构build。

IOW,天真的执行速度非常快,甚至没有什么好笑的:比我的find调用的速度快6倍,这比下一级的方法快了4倍。

保留的教训:衡量总是一件好事(但必须准确); 像splitlines这样的string方法splitlines非常快的方式实现的; 通过编程将string放在一个非常低的位置(尤其是通过+=非常小的循环)可能会非常缓慢。

编辑 :@雅各布的build议,稍加修改,以提供相同的结果作为其他人(尾随空白保留),即:

 from cStringIO import StringIO def f4(foo=foo): stri = StringIO(foo) while True: nl = stri.readline() if nl != '': yield nl.strip('\n') else: raise StopIteration 

测量给出:

 $ python -mtimeit -s'import asp' 'list(asp.f4())' 1000 loops, best of 3: 406 usec per loop 

.find基于.find的方法 – 仍然值得记住,因为它可能不太容易产生小的错误(任何出现+1和-1的循环,就像我上面的f3一样,应该自动触发一个怀疑 – 所以应该有许多循环缺乏这种调整,应该有他们 – 虽然我相信我的代码也是正确的,因为我能够检查其他function的输出)。

但是基于分割的方法仍然有效。

一边说: f4可能更好的风格是:

 from cStringIO import StringIO def f4(foo=foo): stri = StringIO(foo) while True: nl = stri.readline() if nl == '': break yield nl.strip('\n') 

至less,这是一个较less的冗长。 不幸的是,我们不得不去掉\n s,这样就不能更清楚和快速地将while循环replace成return iter(stri) (在Python的现代版本中它是多余的,我相信从2.3或2.4开始,但它也是无害的) 。 也许值得一试,也是:

  return itertools.imap(lambda s: s.strip('\n'), stri) 

或其变化 – 但我在这里停下来,因为这几乎是一个基于strip ,最简单,最快的理论练习之一。

我不确定你的意思是“再次由parsing器”。 分割完成后,不再遍历string ,只遍历拆分string列表 。 这可能是实现这个目标的最快方法,只要string的大小不是绝对的大。 python使用不可变string的事实意味着你必须总是创build一个新的string,所以这个必须在某个时候完成。

如果你的string非常大,缺点是内存使用情况:你将在内存中同时拥有原始string和拆分string列表,将所需内存加倍。 一个迭代器的方法可以节省你这个,根据需要build立一个string,虽然它仍然支付“分裂”的惩罚。 但是,如果你的string很大,你通常要避免内存中的非分裂string。 从文件中读取string会更好,它已经允许您以行的forms遍历它。

但是,如果你已经在内存中有一个巨大的string,一种方法是使用StringIO,它为string提供类似文件的接口,包括允许按行迭代(在内部使用.find来查找下一个换行符)。 您然后得到:

 import StringIO s = StringIO.StringIO(myString) for line in s: do_something_with(line) 

如果我正确地读了Modules/cStringIO.c ,这应该是非常有效的(尽pipe有些冗长):

 from cStringIO import StringIO def iterbuf(buf): stri = StringIO(buf) while True: nl = stri.readline() if nl != '': yield nl.strip() else: raise StopIteration 

我想你可以推出自己的:

 def parse(string): retval = '' for char in string: retval += char if not char == '\n' else '' if char == '\n': yield retval retval = '' if retval: yield retval 

我不确定这个实现有多高效,但是这只会遍历你的string一次。

嗯,发电机。

编辑:

当然,你也可以添加任何types的分析动作,但这很简单。

基于正则expression式的search有时比生成器方法更快:

 RRR = re.compile(r'(.*)\n') def f4(arg): return (i.group(1) for i in RRR.finditer(arg))