迭代字符串的行

Bjö*_*lex 110 python string iterator

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

foo = """
this is 
a multi-line string.
"""
Run Code Online (Sandbox Code Playgroud)

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

lineiterator = iter(foo.splitlines())
Run Code Online (Sandbox Code Playgroud)

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

Ale*_*lli 133

这有三种可能性:

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())
Run Code Online (Sandbox Code Playgroud)

将此作为主脚本运行确认三个函数是等效的.随着timeit(和* 100foo获得更精确的测量大幅度的字符串):

$ 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
Run Code Online (Sandbox Code Playgroud)

注意我们需要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
Run Code Online (Sandbox Code Playgroud)

测量给出:

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

不像.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')
Run Code Online (Sandbox Code Playgroud)

至少,它有点不那么冗长.\n遗憾的是,剥离尾随的需要禁止更清晰和更快地替换while循环return iter(stri)(iter在现代版本的Python中,我认为这部分是多余的,我相信自2.3或2.4,但它也是无害的).也许值得一试,还有:

    return itertools.imap(lambda s: s.strip('\n'), stri)
Run Code Online (Sandbox Code Playgroud)

或其变化 - 但我停在这里,因为它几乎是一个strip基于,最简单和最快的理论练习.

  • 内存消耗怎么样?`split()`清楚地交换内存的性能,除了列表的结构之外还保留了所有部分的副本. (5认同)
  • 我最初对你的评论感到困惑,因为你按照实现和编号的相反顺序列出了时序结果.= P (3认同)

Bri*_*ian 50

我不确定你的意思是"然后再由解析器".分割完成后,没有进一步遍历字符串,只遍历分割字符串列表.这可能实际上是实现这一目标的最快方法,只要字符串的大小不是很大.python使用不可变字符串这一事实意味着您必须始终创建一个新字符串,因此无论如何必须在某些时候完成.

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

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

import StringIO
s = StringIO.StringIO(myString)
for line in s:
    do_something_with(line)
Run Code Online (Sandbox Code Playgroud)

  • 注意:对于python 3,您必须为此使用io包,例如,使用io.StringIO而不是StringIO.StringIO。参见https://docs.python.org/3/library/io.html (2认同)

Tom*_*dor 9

您可以迭代“文件”,它会生成行,包括尾随换行符。要从字符串中创建“虚拟文件”,您可以使用StringIO

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

for line in io.StringIO(foo):
    print(repr(line))
Run Code Online (Sandbox Code Playgroud)