Как преодолеть проблему с памятью при последовательном добавлении файлов друг к другу

Я запускаю следующий скрипт, чтобы добавлять файлы друг к другу, циклически меняя месяцы и годы, если файл существует, я только что протестировал его с большим набором данных, где я ожидаю, что выходной файл будет размером примерно 600 МБ. Однако у меня проблемы с памятью. Во-первых, нормально ли сталкиваться с проблемами памяти (у моего компьютера 8 ГБ оперативной памяти). Я не уверен, как я съедаю все это пространство памяти?

Код, который я запускаю

import datetime,  os
import StringIO

stored_data = StringIO.StringIO()

start_year = "2011"
start_month = "November"
first_run = False

current_month = datetime.date.today().replace(day=1)
possible_month = datetime.datetime.strptime('%s %s' % (start_month, start_year), '%B %Y').date()
while possible_month <= current_month:
    csv_filename = possible_month.strftime('%B %Y') + ' MRG.csv'
    if os.path.exists(csv_filename):
        with open(csv_filename, 'rb') as current_csv:
            if first_run != False:
                next(current_csv)
            else:
                first_run = True
            stored_data.writelines(current_csv)
    possible_month = (possible_month + datetime.timedelta(days=31)).replace(day=1)
if stored_data:
    contents = stored_data.getvalue()
    with open('FullMergedData.csv', 'wb') as output_csv:
        output_csv.write(contents)

Трекбек, который я получаю:

Traceback (most recent call last):
  File "C:\code snippets\FullMerger.py", line 23, in <module>
    contents = stored_output.getvalue()
  File "C:\Python27\lib\StringIO.py", line 271, in getvalue
    self.buf += ''.join(self.buflist)
MemoryError

Любые идеи, как решить эту проблему или сделать этот код более эффективным для решения этой проблемы. Большое спасибо
АЕА

Редактировать1

После запуска кода, предоставленного alKid, я получил следующую трассировку.

Traceback (most recent call last):
  File "C:\FullMerger.py", line 22, in <module>
    output_csv.writeline(line)
AttributeError: 'file' object has no attribute 'writeline'

Я исправил вышеуказанное, изменив его на writelines, однако я все еще получил следующую трассировку.

Traceback (most recent call last):
  File "C:\FullMerger.py", line 19, in <module>
    next(current_csv)
StopIteration

person AEA    schedule 01.11.2013    source источник
comment
Не могли бы вы написать напрямую в csv вместо того, чтобы сохранять данные в памяти, а затем записывать?   -  person SethMMorton    schedule 01.11.2013
comment
@SethMMorton да, потенциально, но я стараюсь избегать слишком большого количества записей в файл. Приведенный выше сценарий для небольших файлов данных выполняется мгновенно, в то время как другие методы, включающие многократное чтение и запись, оказались медленными.   -  person AEA    schedule 01.11.2013


Ответы (2)


В stored_data вы пытаетесь сохранить весь файл, и, поскольку он слишком велик, вы получаете отображаемую ошибку.

Одним из решений является запись файла построчно. Это намного эффективнее с точки зрения использования памяти, поскольку в буфере хранится только строка данных, а не все 600 МБ.

Вкратце, структура может быть примерно такой:

with open('FullMergedData.csv', 'a') as output_csv: #this will append  
# the result to the file.
    with open(csv_filename, 'rb') as current_csv:
        for line in current_csv:   #loop through the lines
            if first_run != False:
                next(current_csv)
                first_run = True #After the first line,
                #you should immidiately change first_run to true.
            output_csv.writelines(line)  #write it per line

Должно исправить вашу проблему. Надеюсь это поможет!

person aIKid    schedule 01.11.2013
comment
Спасибо за ответ, я не знал, как обновить свой код идеями, опубликованными в моем старом вопросе, поэтому я ввел здесь свой полный код. Я добавил трассировку к моему вопросу. Большое спасибо - person AEA; 01.11.2013
comment
Ах, вы никогда не меняли first_run на True. - person aIKid; 01.11.2013
comment
Кроме того, если это решит ваш вопрос, пожалуйста, проголосуйте и примите :) - person aIKid; 01.11.2013
comment
Привет, @alKid, хотя это исправило трассировку, код не работает должным образом, этот код не запоминает последний файл при циклическом просмотре разных файлов, т. Е. Он перезаписывает его при циклическом переходе к следующему ежемесячному файлу. - person AEA; 01.11.2013
comment
Да, действительно, и это ни в коем случае не быстро. Исходный код был мгновенным, это займет несколько минут (не идеально) - person AEA; 01.11.2013
comment
Как работал исходный код? Вы храните все данные в буфере? - person aIKid; 01.11.2013
comment
Исправление работало правильно, однако для добавления данных примерно до 600 МБ требуется 3-4 минуты, исходный код существенно делал то же самое, но сохранял его в памяти и записывал все сразу в выходной файл. Используя мой исходный код с половиной моих данных, он создает половину моих данных, он создает файл менее чем за 0,304 секунды, тогда как в вашей версии требуется 49,435, что, как вы видите, значительно медленнее. - person AEA; 01.11.2013
comment
Это так неправильно, поскольку выполнить его за 0,3 секунды невозможно. Пробовали ли вы переместить файл размером 600 мб с флешки на компьютер? Ваш исходный код либо не работает, либо не дает ожидаемого результата. Ты получил за это MemoryError, верно? - person aIKid; 01.11.2013
comment
Я просто запустил его на еще меньшем наборе данных, и время оказалось следующим: AEA Routine took 0.190 seconds и alKid Routine took 2.738 seconds, я использовал сравнение блокнота ++, и оба файла идентичны. Запуск данных объемом 600 МБ получает MemoryError да. - person AEA; 01.11.2013
comment
Для меньших наборов это ясно, так как мой метод предназначен для обработки больших данных, а ваш исходный код должен был обрабатывать только небольшие данные. Но поверьте мне, для вашего большого файла было бы трудно найти более быстрое решение. - person aIKid; 01.11.2013

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

Обновлять

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

skip_first_line = True

with open('FullMergedData.csv', 'a') as output_csv:
    with open(csv_filename, 'rb') as current_csv:
        if skip_first_line:
            current_csv.readline()
        shutil.copyfileobj(current_csv, output_csv)

Обратите внимание, что если вы используете copyfileobj, вы захотите использовать current_csv.readline() вместо next(current_csv). Это связано с тем, что итерация по файловому объекту буферизует часть файла, что обычно очень полезно, но в данном случае вам это не нужно. Подробнее об этом здесь.

person Bi Rico    schedule 01.11.2013
comment
Я просмотрел документацию, но не смог найти подходящего примера. Можете ли вы привести небольшой пример того, как его правильно использовать? - person AEA; 01.11.2013
comment
copyfileobj используется для копирования всего файла, вы должны пропустить, если хотите удалить первую строку. - person aIKid; 01.11.2013
comment
@aIKid copyfileobj начинает копирование с текущей позиции указателя файла. Это означает, что вы можете просто прочитать столько файла, сколько хотите пропустить, а затем начать копирование. Конечно, это означает, что вам нужно следить за любой буферизацией. - person Bi Rico; 01.11.2013