Чтение .txt-файла с эффективной памятью на Python

#python #list #txt

Вопрос:

Я читаю некоторые файлы .txt в виде списков, используя этот метод:

 with open('../Results/DIMP_1120.txt', 'r') as f:
    DIMP_1120 = list(csv.reader(f, delimiter="|"))
with open('../Results/DIMP_1121.txt', 'r') as f:
    DIMP_1121 = list(csv.reader(f, delimiter="|"))
with open('../Results/DIMP_1122.txt', 'r') as f:
    DIMP_1122 = list(csv.reader(f, delimiter="|"))
 

Но это занимает почти в 10 раз больше размера файла в оперативной памяти.

Есть ли эффективный способ прочитать его?

После этого я добавлю эти списки и отсортирую их.

 big_list = DIMP_1120   DIMP_1121   DIMP_1122

#Order all lists by *Sorter (Row_id2)
from operator import itemgetter
big_list= sorted(big_list, key=itemgetter(0))
 

Поэтому, я думаю, мне нужно запомнить все списки сразу.

Комментарии:

1. Вам это нужно как list все сразу? Если вы можете обрабатывать его строка за строкой, ему потребуется память только для строки за раз (ну, две строки за раз, учитывая, как работает итерация Python, но достаточно близко). В противном случае, да, накладные расходы Python str составляют ~49 байт за штуку, плюс накладные расходы каждой list оболочки, поэтому, если большинство полей короткие, накладные расходы по отношению к данным будут довольно высокими.

2. Привет, спасибо за ответ. Да, мне нужно все это сразу, потому что я добавляю в другие списки и сортирую их все. Чтение в виде рассола вместо txt может помочь?

3. Маринование не сильно поможет, если базовые данные не могут быть сохранены с использованием более эффективных типов памяти (например, хранение и восстановление необработанных int ). Я добавил примечание о том , как вы можете воспользоваться этим преимуществом, все еще используя csv , просто выполняя преобразования типов по мере загрузки.

4. «добавление в другие списки и сортировка всех из них» не обязательно требует чтения всех сразу. Мы могли бы помочь вам лучше, если бы знали больше деталей.

5. Можете ли вы показать несколько строк данных? Сколько у вас файлов? Насколько велик каждый файл? Что вы делаете с последним большим отсортированным списком?

Ответ №1:

Если вы можете обрабатывать данные по строке за раз, не сохраняя каждую строку, например

 for row in csv.reader(f, delimiter="|"):
 

сделайте это; это единственный способ значительно сократить пиковое использование памяти.

В противном случае, лучшее, что вы можете сделать, это преобразовать строку формата хранения От list До tuple , как Вы читаете, которая должна сохранить хоть немного памяти (больше, если бы csv.reader не усек превышение доступности list делает по умолчанию), а tuple с не overallocate, и они хранят данные, встроенной в объект Python заголовка (без сверхвыделение или дополнительный распределитель округление надбавок), а list с заголовка просто добавляет указатель на отдельно выделенной памяти (которая overallocates и за два округление надбавок); для динамически выделяемых list размер 2 (е.g. в CPython 3.9, где обобщения распаковки ведут себя как последовательные добавления, [*(0, 1)] ) накладные расходы контейнера могут снизиться со 120 байт до 56 байт (возможно, больше , поскольку ошибка округления распределителя не видна sys.getsizeof и list оплачивается дважды, tuple только один раз) просто путем преобразования в tuple , что может иметь значение для миллионов таких строк. Наиболее эффективным средством его преобразования было бы изменение:

 DIMP_1120 = list(csv.reader(f, delimiter="|"))  
 

Для:

 DIMP_1120 = list(map(tuple, csv.reader(f, delimiter="|")))
 

map работает лениво на Python 3, поэтому каждая строка будет прочитана как a list , преобразована в a tuple и сохранена во внешней list до того , как будет прочитана следующая; это не потребует одновременного хранения всего ввода в виде list s и tuple s, даже на мгновение. Если в ваших базовых данных есть некоторые поля, которые могут быть преобразованы заранее в более эффективно хранимый тип (например int ), list понимание того, что поля преобразуются и упаковываются как tuple s вместо list s, может дать больше, например, для четырех полей в строке, последние три из которых логически int являются s, вы могли бы сделать:

 DIMP_1120 = [(a, int(b), int(c), int(d)) for a, b, c, d in csv.reader(f, delimiter="|")]
# If you might have some empty/missized rows you wish to ignore, an if check can discard
# wrong length lists; a nested "loop" over the single item can unpack after checking:
DIMP_1120 = [(a, int(b), int(c), int(d)) for lst in csv.reader(f, delimiter="|")
             if len(lst) == 4
             for a, b, c, d in (lst,)]
 

распаковка list s из csv.reader , преобразование соответствующих полей в int и повторная упаковка как a tuple .

Примечание: Обязательно передайте newline="" (пустую строку) вашему open вызову; csv модуль требует этого для правильной обработки новых строк из разных диалектов CSV.

Обновление: Чтение в отдельные list s, затем объединение, а затем сортировка увеличивает пиковые внешние list накладные расходы с пропорциональности количеству строк до пропорциональности ~2,66 раза количеству строк (при условии, что все файлы имеют одинаковый размер). Вы можете избежать этих накладных расходов, изменив:

 with open('../Results/DIMP_1120.txt', 'r') as f:
    DIMP_1120 = list(csv.reader(f, delimiter="|"))  
with open('../Results/DIMP_1121.txt', 'r') as f:
    DIMP_1121 = list(csv.reader(f, delimiter="|"))  
with open('../Results/DIMP_1122.txt', 'r') as f:
    DIMP_1122 = list(csv.reader(f, delimiter="|"))  

big_list = DIMP_1120   DIMP_1121   DIMP_1122

#Order all lists by *Sorter (Row_id2)
from operator import itemgetter
big_list= sorted(big_list, key=itemgetter(0))
 

Для:

 from itertools import chain

with open('../Results/DIMP_1120.txt', 'r') as f1, 
     open('../Results/DIMP_1121.txt', 'r') as f2, 
     open('../Results/DIMP_1122.txt', 'r') as f3:
    
    ALL_DIMP = chain.from_iterable(csv.reader(f, delimiter="|")
                                   for f in (f1, f2, f3))
    big_list = sorted(map(tuple, ALL_DIMP), key=itemgetter(0))
 

Только один list из них когда-либо создавался (в вашем исходном коде было шесть list s; по одному для каждого входного файла, один для объединения первых двух файлов, один для объединения всех трех файлов и новый для отсортированного объединения всех трех файлов), содержащий все данные, и он создается отсортированным с самого начала.

Я отмечу, что это может быть лучше сделано в командной строке, по крайней мере, в системах, подобных *NIX, где утилита sort командной строки знает, как сортировать огромные файлы по полю, с автоматическим выводом на диск, чтобы избежать одновременного хранения слишком большого объема в памяти. Это можно было бы сделать на Python, но это было бы уродливее (если только для этого не существует какого-нибудь модуля PyPI, о котором я не знаю).

Комментарии:

1. небольшой эксперимент с накладными расходами.

2. @don’talkjustcode: По-прежнему существует нелинейная модель роста; попробуйте ее, 'a,b,c,d,e' и использование памяти для csv.reader вывода данных подскочит с 88 байт до 120 ( tuple затем снова снизится до 80). Похоже, разница в том, как split распределяется (я случайно выбрал пример, который распределяет больше, чем list здание по умолчанию; когда оно не maxsplit предусмотрено, оно резервирует место для 12 элементов спереди и не обрезается в конце). Таким образом, преобразование в tuple может сэкономить память, но не так сильно, как показывало мое сравнение игрушек.

3. Спасибо. Это улучшило использование памяти почти на 30%.

4. Я обновил свой пример, чтобы избежать .split дополнительных превышение доступности (в то же время, умышленно, показав лучший сценарий, где list расходы более чем в два раза tuple над головой; на практике все tuple может гарантировать , что tuple(somelist) будет потреблять не менее 16 байт меньше, чем вызов list(somelist) , который в с CPython 3.9 не включает любое превышение доступности нагрузку на копию somelist (его размеры точно к элементам данного). tuple по-прежнему экономит 16 байт (плюс потеря округления распределителя), избегая хранения указателя и отдельного ssize_t поля емкости.

Ответ №2:

Считывание данных в a list означает, что вы загружаете и сохраняете все строки в памяти. Вместо этого вы можете выполнить итерацию по строкам одну за другой, выполнив итерацию по csv.reader() вместо этого, что, как описано в документе:

csv.reader(csv-файл, диалект=’excel’, **fmtparams)

Возвращает объект чтения, который будет повторять строки в данном csv-файле. csvfile может быть любым объектом, который поддерживает протокол итератора и возвращает строку при каждом вызове метода next ()… Каждая строка, считанная из csv-файла, возвращается в виде списка строк.

 with open('../Results/DIMP_1120.txt', 'r') as f:
    for row in csv.reader(f, delimiter="|")):
        # Process the current line
 

Недостатком этого является то, что у вас есть доступ только к 1 строке одновременно. Но я считаю, что если вы не хотите загружать все сразу в память, это может быть единственным способом. Вам просто нужно изменить свою логику, чтобы обрабатывать все, что нужно сделать в каждой строке.

Комментарии:

1. Привет, спасибо за ответ. Да, мне нужно все это сразу, потому что я добавляю в другие списки и сортирую их все. Чтение в виде рассола вместо txt может помочь?