Вот три возможности:
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
(и a * 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, наивная реализация настолько быстрее, что это даже не смешно: в 6 раз быстрее, чем моя попытка с find
вызовами, что, в свою очередь, в 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
основанным, самым простым и быстрым.
foo.splitlines()
верно?