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(和* 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
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基于,最简单和最快的理论练习.
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)
您可以迭代“文件”,它会生成行,包括尾随换行符。要从字符串中创建“虚拟文件”,您可以使用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)