Как разделить строку и присоединиться к ней, не создавая промежуточный список в Python?

Скажи, что я есть что-то вроде следующего:

dest = "\n".join( [line for line in src.split("\n") if line[:1]!="#"] )

(т.е. обрезать любые строки, начинающиеся с # из многострочной строки src )

src очень большой, поэтому я предполагаю .split () создаст большой промежуточный список. Я могу изменить понимание списка на выражение генератора, но есть ли какой-то "xsplit", который я могу использовать, чтобы работать только на одной строке за раз? Правильно ли мое предположение? Какой самый эффективный (эффективный для памяти) способ справиться с этим?

Пояснение : Это произошло из-за того, что моему коду не хватило памяти. Я знаю, что есть способы полностью переписать мой код, чтобы обойти это, но вопрос в Python: есть ли версия split () (или эквивалентная идиома), которая ведет себя как генератор и, следовательно, не делает дополнительную работу копия SRC ?

9
задан Tom 23 August 2010 в 09:42
поделиться

5 ответов

Вот способ сделать общий тип разделения с помощью itertools

>>> import itertools as it
>>> src="hello\n#foo\n#bar\n#baz\nworld\n"
>>> line_gen = (''.join(j) for i,j in it.groupby(src, "\n".__ne__) if i)
>>> '\n'.join(s for s in line_gen if s[0]!="#")
'hello\nworld'

groupby обрабатывает каждый char в src отдельно, поэтому производительность, вероятно, не звездная, но она позволяет избежать создания каких-либо промежуточных огромных структур данных

Вероятно, лучше потратить несколько строк и создать генератор

>>> src="hello\n#foo\n#bar\n#baz\nworld\n"
>>>
>>> def isplit(s, t): # iterator to split string s at character t
...     i=j=0
...     while True:
...         try:
...             j = s.index(t, i)
...         except ValueError:
...             if i<len(s):
...                 yield s[i:]
...             raise StopIteration
...         yield s[i:j]
...         i = j+1
...
>>> '\n'.join(x for x in isplit(src, '\n') if x[0]!='#')
'hello\nworld'

re имеет метод под названием finditer , который также может быть использован для этой цели

>>> import re
>>> src="hello\n#foo\n#bar\n#baz\nworld\n"
>>> line_gen = (m.group(1) for m in re.finditer("(.*?)(\n|$)",src))
>>> '\n'.join(s for s in line_gen if not s.startswith("#"))
'hello\nworld'

сравнение производительности - это упражнение для OP, чтобы попробовать по реальным данным

5
ответ дан 4 December 2019 в 12:15
поделиться

В существующем коде вы можете изменить список на выражение генератора:

dest = "\n".join(line for line in src.split("\n") if line[:1]!="#")

Это очень небольшое изменение позволяет избежать создания одного из двух временных списков в вашем коде и требует никаких усилий с вашей стороны.

Совершенно другой подход, позволяющий избежать временного построения обоих списков, заключается в использовании регулярного выражения:

import re
regex = re.compile('^#.*\n?', re.M)
dest = regex.sub('', src)

Это не только позволит избежать создания временных списков, но и позволит избежать создания временных строк для каждой строки ввода. Вот некоторые измерения производительности предлагаемых решений:

init = r'''
import re, StringIO
regex = re.compile('^#.*\n?', re.M)
src = ''.join('foo bar baz\n' for _ in range(100000))
'''

method1 = r'"\n".join([line for line in src.split("\n") if line[:1] != "#"])'
method2 = r'"\n".join(line for line in src.split("\n") if line[:1] != "#")'
method3 = 'regex.sub("", src)'
method4 = '''
buffer = StringIO.StringIO(src)
dest = "".join(line for line in buffer if line[:1] != "#")
'''

import timeit

for method in [method1, method2, method3, method4]:
    print timeit.timeit(method, init, number = 100)

Результаты:

 9.38s   # Split then join with temporary list
 9.92s   # Split then join with generator
 8.60s   # Regular expression
64.56s   # StringIO

Как видите, регулярное выражение - самый быстрый метод.

Из ваших комментариев я вижу, что вы на самом деле не заинтересованы в том, чтобы избегать создания временных объектов. Что вы действительно хотите, так это уменьшить требования к памяти для вашей программы. Временные объекты не обязательно влияют на потребление памяти вашей программой, поскольку Python хорош в быстрой очистке памяти. Проблема возникает из-за наличия объектов, которые сохраняются в памяти дольше, чем им необходимо, и все эти методы имеют эту проблему.

Если у вас все еще заканчивается память, я бы посоветовал вам не выполнять эту операцию полностью в памяти. Вместо этого сохраните ввод и вывод в файлах на диске и считывайте из них в потоковом режиме. Это означает, что вы читаете одну строку из ввода, записываете строку в вывод, читаете строку, записываете строку и т. Д. Это создаст множество временных строк, но даже в этом случае это не потребует почти никакой памяти, потому что вам нужно только обработать струны по одной.

4
ответ дан 4 December 2019 в 12:15
поделиться

Проблема в том, что строки в python неизменяемы, поэтому будет очень сложно что-либо сделать без промежуточного хранилища.

1
ответ дан 4 December 2019 в 12:15
поделиться
buffer = StringIO(src)
dest = "".join(line for line in buffer if line[:1]!="#")

Конечно, это действительно имеет смысл, если вы используете StringIO повсюду. Он работает в основном так же, как файлы. Вы можете искать, читать, писать, выполнять итерацию (как показано) и т. Д.

5
ответ дан 4 December 2019 в 12:15
поделиться

Если я правильно понимаю ваш вопрос о «более общих вызовах split ()», вы можете использовать re.finditer , например:

output = ""

for i in re.finditer("^.*\n",input,re.M):
    i=i.group(0).strip()
    if i.startswith("#"):
        continue
    output += i + "\n"

Здесь вы можете заменить регулярное выражение на что-то более сложное.

2
ответ дан 4 December 2019 в 12:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: