У меня есть многострочная строка, определенная следующим образом:
foo = """
this is
a multi-line string.
"""
Эту строку мы использовали в качестве тестового ввода для синтаксического анализатора, который я пишу. Функция-синтаксический анализатор получает file
-объект в качестве входных данных и выполняет итерацию по нему. Он также вызывает next()
метод напрямую, чтобы пропустить строки, поэтому мне действительно нужен итератор в качестве ввода, а не итерация. Мне нужен итератор, который выполняет итерацию по отдельным строкам этой строки, как file
-object, по строкам текстового файла. Конечно, я мог бы сделать это так:
lineiterator = iter(foo.splitlines())
Есть ли более прямой способ сделать это? В этом сценарии строка должна пройти один раз для разделения, а затем еще раз для синтаксического анализа. В моем тестовом примере это не имеет значения, поскольку строка там очень короткая, я просто спрашиваю из любопытства. Python имеет так много полезных и эффективных встроенных программ для таких вещей, но я не смог найти ничего, что соответствовало бы этой потребности.
foo.splitlines()
верно?splitlines()
и второй раз, повторяя результат этого метода.Ответы:
Вот три возможности:
Запуск этого сценария в качестве основного подтверждает, что все три функции эквивалентны. С
timeit
(и a* 100
дляfoo
получения существенных строк для более точного измерения):Обратите внимание, что нам нужен
list()
вызов, чтобы гарантировать, что итераторы пройдены, а не просто построены.IOW, наивная реализация настолько быстрее, что это даже не смешно: в 6 раз быстрее, чем моя попытка с
find
вызовами, что, в свою очередь, в 4 раза быстрее, чем подход нижнего уровня.Уроки, которые следует запомнить: измерение - это всегда хорошо (но оно должно быть точным); строковые методы вроде
splitlines
реализованы очень быстро; соединение строк путем программирования на очень низком уровне (особенно петлями+=
из очень маленьких кусочков) может быть довольно медленным.Изменить : добавлено предложение @Jacob, слегка измененное, чтобы дать те же результаты, что и другие (конечные пробелы в строке сохраняются), то есть:
Измерение дает:
не так хорош, как
.find
основанный на подходе, - тем не менее, о нем стоит помнить, потому что он может быть менее подвержен мелким единичным ошибкам (любой цикл, в котором вы видите вхождения +1 и -1, какf3
описано выше, должен автоматически запускать одно за другим подозрения - как и многие циклы, которые не имеют таких настроек и должны иметь их - хотя я считаю, что мой код также верен, поскольку я мог проверить его вывод с помощью других функций »).Но подход, основанный на разделении, по-прежнему актуален.
В стороне: возможно, лучший стиль для
f4
:по крайней мере, это немного менее многословно. К
\n
сожалению, необходимость убрать завершающие символы запрещает более четкую и быструю заменуwhile
цикла наreturn iter(stri)
(iter
часть, из которой избыточна в современных версиях Python, я считаю, начиная с 2.3 или 2.4, но это также безобидно). Возможно, стоит попробовать:или их вариации - но я останавливаюсь на этом, поскольку это в значительной степени теоретическое упражнение по сравнению с
strip
основанным, самым простым и быстрым.источник
(line[:-1] for line in cStringIO.StringIO(foo))
это довольно быстро; почти так же быстро, как наивная реализация, но не совсем.timeit
привычку.list
вызова для фактического времени всех соответствующих частей! -).split()
явно жертвует памятью на производительность, храня копии всех разделов в дополнение к структурам списка.Я не уверен, что вы имеете в виду, говоря "затем снова с помощью парсера". После того, как разделение было выполнено, дальнейший обход строки не выполняется , только выполняется обход списка разделенных строк. Это, вероятно, будет самым быстрым способом добиться этого, если размер вашей строки не будет абсолютно огромным. Тот факт, что python использует неизменяемые строки, означает, что вы всегда должны создавать новую строку, так что это должно быть сделано в какой-то момент в любом случае.
Если ваша строка очень большая, недостаток заключается в использовании памяти: у вас в памяти одновременно будет исходная строка и список разделенных строк, что удвоит требуемую память. Подход с итератором может спасти вас от этого, создавая строку по мере необходимости, хотя он все равно платит штраф за «разбиение». Однако, если ваша строка настолько велика, вы обычно хотите, чтобы даже неразделенная строка находилась в памяти. Лучше было бы просто прочитать строку из файла, который уже позволяет вам перебирать ее как строки.
Однако, если у вас уже есть огромная строка в памяти, одним из подходов будет использование StringIO, который представляет файловый интерфейс для строки, включая возможность итерации по строке (внутреннее использование .find для поиска следующей новой строки). Тогда вы получите:
источник
io
для этого пакет, например, используйтеio.StringIO
вместоStringIO.StringIO
. См. Docs.python.org/3/library/io.htmlStringIO
- также хороший способ получить универсальную высокопроизводительную обработку новой строки.Если я
Modules/cStringIO.c
правильно прочитал , это должно быть довольно эффективно (хотя и несколько многословно):источник
Поиск на основе регулярных выражений иногда быстрее, чем подход генератора:
источник
Я полагаю, вы могли бы свернуть свой собственный:
Я не уверен, насколько эффективна эта реализация, но она будет повторять вашу строку только один раз.
Ммм, генераторы.
Редактировать:
Конечно, вы также захотите добавить любой тип действий по синтаксическому анализу, который вы хотите предпринять, но это довольно просто.
источник
+=
части наихудшаяO(N squared)
, хотя некоторые приемы реализации пытаются ее снизить, когда это возможно)..join
метод фактически выглядит как сложность O (N). Поскольку я еще не смог найти конкретное сравнение, сделанное на SO, я начал вопрос stackoverflow.com/questions/3055477/… (на который неожиданно было получено больше ответов, чем только мой собственный!)Вы можете перебирать «файл», в результате чего получаются строки, включая завершающий символ новой строки. Чтобы создать «виртуальный файл» из строки, вы можете использовать
StringIO
:источник