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

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

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

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

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

9
Tom 23 Авг 2010 в 11:52

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 обрабатывает каждый символ в 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'

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

5
John La Rooy 23 Авг 2010 в 11:02

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

1
Andrew Jaffe 23 Авг 2010 в 07:54

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

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
Mark Byers 23 Авг 2010 в 09:04
buffer = StringIO(src)
dest = "".join(line for line in buffer if line[:1]!="#")

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

5
Matthew Flaschen 23 Авг 2010 в 21:34

Если я правильно понимаю ваш вопрос о «более общих вызовах 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
loevborg 23 Авг 2010 в 10:24