迭代字符串的行

时间:2010-06-16 15:13:55

标签: python string iterator

我有一个像这样定义的多行字符串:

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

这个字符串我们用作我正在编写的解析器的测试输入。解析器函数接收一个file - 对象作为输入并迭代它。它也直接调用next()方法来跳过行,所以我真的需要一个迭代器作为输入,而不是迭代。 我需要一个迭代器,迭代遍历该字符串的各个行,就像file - 对象将遍历文本文件的行。我当然可以这样做:

lineiterator = iter(foo.splitlines())

有更直接的方法吗?在这种情况下,字符串必须遍历一次以进行拆分,然后再由解析器遍历。在我的测试用例中没关系,因为那里的字符串很短,我只是出于好奇而问。 Python为这些东西提供了许多有用且高效的内置插件,但我找不到任何适合这种需求的东西。

6 个答案:

答案 0 :(得分:120)

以下是三种可能性:

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(和* 100 foo来获得更多精确测量的实质字符串:

$ 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()调用以确保遍历迭代器,而不仅仅是构建。

IOW,天真的实现速度要快得多,甚至不是很有趣:比我find次调用的速度慢6倍,而后者的速度比低级方法快4倍。

保留的教训:测量始终是一件好事(但必须准确);像splitlines这样的字符串方法以非常快的方式实现;通过在非常低的级别(尤其是+=非常小的部分的循环)编程将字符串放在一起可能会非常慢。

编辑:添加@ Jacob的提议,略微修改以提供与其他提案相同的结果(保留一行上的尾随空白),即:

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的方法那么好 - 仍然值得记住,因为它可能不太容易出现小的一个一个错误(任何循环,你看到出现+1和 - 1,就像我上面的f3一样,应该自动触发一个怀疑 - 所以应该有很多循环没有这样的调整并且应该有它们 - 尽管我相信我的代码也是正确的,因为我能够用其他函数检查其输出')。

但基于分裂的方法仍然有规律。

旁白:f4可能更好的风格是:

from cStringIO import StringIO

def f4(foo=foo):
    stri = StringIO(foo)
    while True:
        nl = stri.readline()
        if nl == '': break
        yield nl.strip('\n')
至少,它有点不那么冗长。不幸的是,删除尾随\n的需要禁止用while更清楚和更快地替换return iter(stri)循环(iter部分在现代版本的Python中是多余的,我相信自2.3或2.4,但它也是无害的)。也许值得一试:

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

或其变体 - 但我停在这里,因为它几乎是strip基于最简单,最快速的理论练习。

答案 1 :(得分:47)

我不确定你的意思是“然后再由解析器”。分割完成后,字符串没有进一步遍历,只有遍历分割字符串的列表。这可能实际上是实现这一目标的最快方法,只要字符串的大小不是很大。 python使用不可变字符串的事实意味着你必须总是创建一个新字符串,所以这必须在某个时候完成。

如果您的字符串非常大,则缺点在于内存使用:您将同时拥有原始字符串和内存中的拆分字符串列表,从而使所需的内存增加一倍。迭代器方法可以为您节省这一点,根据需要构建一个字符串,尽管它仍然会支付“分裂”惩罚。但是,如果您的字符串很大,您通常希望避免连接 unsplit 字符串在内存中。最好只读取文件中的字符串,它已经允许您以行的形式迭代它。

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

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

答案 2 :(得分:3)

如果我正确阅读Modules/cStringIO.c,这应该非常有效(尽管有点冗长):

from cStringIO import StringIO

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

答案 3 :(得分:3)

基于正则表达式的搜索有时比生成器方法更快:

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

答案 4 :(得分:1)

我想你可以自己动手:

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

我不确定这个实现的效率如何,但这只会迭代你的字符串一次。

嗯,发电机。

编辑:

当然,你也想要添加你想要的任何类型的解析动作,但这很简单。

答案 5 :(得分:0)

您可以遍历“文件”,该文件将产生包括尾随换行符在内的行。要使用字符串制作“虚拟文件”,可以使用StringIO

import io  # for Py2.7 that would be import cStringIO as io

for line in io.StringIO(foo):
    print(repr(line))