Iterate over the lines of a string
我有一个这样定义的多行字符串:
1 2 3 4 | foo =""" this is a multi-line string. """ |
这个字符串用作我正在编写的解析器的测试输入。parser函数接收一个
1 | lineiterator = iter(foo.splitlines()) |
号
有更直接的方法吗?在这个场景中,字符串必须遍历一次以进行拆分,然后由解析器再次遍历。在我的测试用例中,这并不重要,因为字符串在那里非常短,我只是出于好奇而问。python有这么多有用和高效的内置组件,但是我找不到适合这种需求的东西。
有三种可能性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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 == ' ' else '' if char == ' ': yield retval retval = '' if retval: yield retval def f3(foo=foo): prevnl = -1 while True: nextnl = foo.find(' ', 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()) |
作为主脚本运行这个命令可以确认这三个函数是等效的。使用
1 2 3 4 5 6 | $ 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 |
。
注意,我们需要
噢,简单的实现速度要快得多,甚至都不好笑:比我使用
要记住的教训:测量总是一件好事(但必须是准确的);像
编辑:添加了@jacob的建议,稍作修改,以获得与其他建议相同的结果(保留行尾空格),即:
1 2 3 4 5 6 7 8 9 10 11 | from cStringIO import StringIO def f4(foo=foo): stri = StringIO(foo) while True: nl = stri.readline() if nl != '': yield nl.strip(' ') else: raise StopIteration |
测量给出:
1 2 | $ python -mtimeit -s'import asp' 'list(asp.f4())' 1000 loops, best of 3: 406 usec per loop |
。
虽然不如基于
但是基于分裂的方法仍然是规则。
旁白:对于
1 2 3 4 5 6 7 8 9 | from cStringIO import StringIO def f4(foo=foo): stri = StringIO(foo) while True: nl = stri.readline() if nl == '': break yield nl.strip(' ') |
。
至少,它没有那么冗长。不幸的是,需要去掉尾随的
1 2 | return itertools.imap(lambda s: s.strip(' '), stri) |
或者说它的变化——但我在这里停下来,因为这几乎是一个理论练习,基于
我不知道你所说的"然后又是解析器"是什么意思。拆分完成后,不再遍历字符串,只遍历拆分字符串列表。这可能是实现这一点的最快方法,只要字符串的大小不是绝对巨大的。事实上,python使用不可变的字符串意味着您必须始终创建一个新的字符串,所以无论如何,这必须在某个时刻完成。
如果字符串非常大,缺点是内存使用:您将在内存中同时拥有原始字符串和拆分字符串列表,从而使所需内存翻倍。迭代器方法可以节省您的开销,根据需要构建一个字符串,尽管它仍然要支付"拆分"的代价。但是,如果字符串太大,则通常希望避免内存中甚至存在未拆分的字符串。最好只是从一个文件中读取字符串,它已经允许您以行的形式进行迭代。
但是,如果内存中已经有一个很大的字符串,一种方法是使用Stringio,它向字符串提供一个类似文件的接口,包括允许按行迭代(在内部使用.find查找下一个换行符)。然后你会得到:
1 2 3 4 | import StringIO s = StringIO.StringIO(myString) for line in s: do_something_with(line) |
。
基于regex的搜索有时比生成器方法更快:
1 2 3 4 | RRR = re.compile(r'(.*) ') def f4(arg): return (i.group(1) for i in RRR.finditer(arg)) |
如果我正确地阅读了
1 2 3 4 5 6 7 8 9 10 | from cStringIO import StringIO def iterbuf(buf): stri = StringIO(buf) while True: nl = stri.readline() if nl != '': yield nl.strip() else: raise StopIteration |
号
我想你可以自己滚:
1 2 3 4 5 6 7 8 9 10 11 | def parse(string): retval = '' for char in string: retval += char if not char == ' ' else '' if char == ' ': yield retval retval = '' if retval: yield retval |
我不确定这个实现有多有效,但它只会在您的字符串上迭代一次。
嗯,发电机。
编辑:
当然,您还需要添加您想要执行的任何类型的解析操作,但这非常简单。