以下是三种可能性:
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()
函数以确保迭代器被遍历,而不仅仅是构建。
换言之,简单实现方式快得让人难以置信:比我使用
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)
(在Python的现代版本中,我相信从2.3或2.4开始就不再需要iter
部分,但也是无害的)。也许也值得尝试:
return itertools.imap(lambda s: s.strip('\n'), stri)
或者类似的变体 - 但是我在这里停止,因为这基本上是关于strip
最简单和最快的理论练习。
foo.splitlines()
进行迭代吗? - SilentGhostsplitlines()
方法进行迭代,就不要再迭代这个方法的结果。 - Felix Kling