Вот три варианта:
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()
, чтобы гарантировать, что итераторы пройдены, а не тольковстроенный.
Итак, наивная реализация намного быстрее, это даже не смешно: в 6 раз быстрее, чем моя попытка вызова find
, что, в свою очередь, в 4 раза быстрее, чем подход более низкого уровня.
Уроки, которые следует сохранить: измерение всегда хорошо (но должно быть точным);строковые методы, такие как splitlines
, реализуются очень быстро;объединение строк путем программирования на очень низком уровне (особенно с помощью циклов +=
очень маленьких кусочков) может быть довольно медленным.
Редактировать : добавлено предложение @ Джейкоба, слегка измененодать те же результаты, что и другие (конечные пробелы в строке сохраняются), то есть:
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
, самое простое и быстрое.