Итерация по строкам строки
у меня есть многострочная строка определена как:
foo = """
this is
a multi-line string.
"""
эта строка, которую мы использовали в качестве тестового ввода для парсера, который я пишу. Парсер-функция получает file-объект в качестве входных данных и перебирает его. Он также вызывает next() метод непосредственно для пропуска строк, поэтому мне действительно нужен итератор в качестве входных данных, а не итерационный.
Мне нужен итератор, который повторяет отдельные строки этой строки, как file - объект будет над строками текстового файла. Я мог бы конечно сделать это так:
lineiterator = iter(foo.splitlines())
есть ли более прямой способ сделать это? В этом случае строка должна быть пройдена один раз для разделения, а затем снова с помощью синтаксического анализатора. Это не имеет значения в моем тестовом случае, так как строка там очень короткая, я просто спрашиваю из любопытства. Python имеет очень много полезных и эффективных встроенных модулей для таких вещей, но я не мог найти ничего, что удовлетворяет этой потребности.
5 ответов:
вот три возможности:
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()вызов, чтобы убедиться, что итераторы пройдены, а не просто построены.IOW, наивная реализация настолько быстрее, что это даже не смешно: в 6 раз быстрее, чем моя попытка с
findзвонки, что в свою очередь в 4 раза быстрее, чем подход более низкого уровня.уроки для сохранения: измерение всегда хорошо (но должно быть точным); строковые методы, такие как
splitlinesреализуются очень быстрыми способами; объединение строк путем программирования на очень низком уровне (esp. по петлям+=очень маленьких кусочков) может быть довольно медленным.Edit: добавлено предложение @ 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основанный подход -- тем не менее, стоит иметь в виду, потому что он может быть менее склонен к небольшим ошибкам off-by-one (любой цикл, где вы видите вхождения +1 и -1, например myf3выше, должен автоматически вызывать подозрения off-by-one - и так должно быть много петель, которые не имеют таких настроек и должны иметь их-хотя я считаю, что мой код также прав, так как я был возможность проверить его выход с другими функциями').но сплит - подход по-прежнему правил.
в сторону: возможно, лучший стиль для
f4будет:from cStringIO import StringIO def f4(foo=foo): stri = StringIO(foo) while True: nl = stri.readline() if nl == '': break yield nl.strip('\n')по крайней мере, это немного менее многословный. Нужно раздеть трейлинг
\ns К сожалению запрещает более четкую и быструю заменуwhileпетли сreturn iter(stri)(theiterчасть которого избыточна в современных версиях Python, я считаю, начиная с 2.3 или 2.4, но это также безобидный.) Может быть, стоит попробовать, также:return itertools.imap(lambda s: s.strip('\n'), stri)или его вариации - но я останавливаюсь здесь, так как это в значительной степени теоретическое упражнение wrt
stripна основе, простой и быстрый, один.
Я не уверен, что вы подразумеваете под "потом опять парсером". После того, как разделение было сделано, нет никакого дальнейшего обхода строка, только обход список разделенных строк. Вероятно, это будет самый быстрый способ сделать это, если размер вашей строки не является абсолютно огромным. Тот факт, что python использует неизменяемые строки, означает, что вы должны всегда создает новую строку, так что это должно быть сделано в какой-то момент в любом случае.
Если ваша строка очень большая, недостатком является использование памяти: вы будете иметь исходную строку и список разделенных строк в памяти одновременно, удваивая требуемую память. Итераторный подход может сэкономить вам это, построив строку по мере необходимости, хотя он по-прежнему платит штраф за "расщепление". Однако, если ваша строка настолько велика, вы обычно хотите избежать даже unsplit строка в памяти. Было бы лучше просто прочитать строку из файла, который уже позволяет вам перебирать его как строки.
однако, если у вас уже есть огромная строка в памяти, одним из подходов было бы использовать StringIO, который представляет файловый интерфейс для строки, включая разрешение итерации по строке (внутреннее использование .найти, чтобы найти следующую строку). Вы тогда получите:
import StringIO s = StringIO.StringIO(myString) for line in s: do_something_with(line)
Если я читаю
Modules/cStringIO.cправильно, это должно быть довольно эффективно (хотя и несколько многословно):from cStringIO import StringIO def iterbuf(buf): stri = StringIO(buf) while True: nl = stri.readline() if nl != '': yield nl.strip() else: raise StopIteration
поиск на основе регулярных выражений иногда быстрее, чем подход генератора:
RRR = re.compile(r'(.*)\n') def f4(arg): return (i.group(1) for i in RRR.finditer(arg))
Я полагаю, вы могли бы свернуть свой собственный:
def parse(string): retval = '' for char in string: retval += char if not char == '\n' else '' if char == '\n': yield retval retval = '' if retval: yield retvalЯ не уверен, насколько эффективна эта реализация, но это будет только повторять вашу строку один раз.
МММ, генераторов.
Edit:
конечно, вы также хотите добавить в любой тип разбора действий, которые вы хотите принять, но это довольно просто.
Comments