Как читать большой файл - построчно?

536

Я хочу перебрать каждую строку всего файла. Один из способов сделать это - прочитать весь файл, сохранить его в списке, а затем перейти по интересующей линии. Этот метод использует много памяти, поэтому я ищу альтернативу.

Мой код до сих пор:

for each_line in fileinput.input(input_file):
    do_something(each_line)

    for each_line_again in fileinput.input(input_file):
        do_something(each_line_again)

Выполнение этого кода выдает сообщение об ошибке: device active.

Какие-либо предложения?

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

384X21
источник
4
Зачем вам нужно читать весь файл снова для каждой строки? Может быть, если вы скажете, что вы пытаетесь достичь, кто-то может предложить лучший подход.
JJJ

Ответы:

1269

Правильный, полностью Pythonic способ чтения файла заключается в следующем:

with open(...) as f:
    for line in f:
        # Do something with 'line'

В withзаявлении ручки открытия и закрытия файла, в том числе , если возбуждается исключение во внутреннем блоке. Он for line in fрассматривает файловый объект fкак итеративный, который автоматически использует буферизованный ввод-вывод и управление памятью, поэтому вам не нужно беспокоиться о больших файлах.

Должен быть один - и желательно только один - очевидный способ сделать это.

Katriel
источник
14
да, это лучшая версия с python 2.6 и выше
Simon Bergot
3
Я лично предпочитаю генераторы и сопрограммы для работы с конвейерами данных.
jldupont
4
Какова будет лучшая стратегия, если файл представляет собой огромный текстовый файл, но с одной строкой, и идея заключается в обработке слов?
mfcabrera
4
Может кто-нибудь объяснить, как for line in f:работает? Я имею в виду, как возможна перебор файлового объекта?
Хак
11
Если вы перебираете объект, Python ищет в списке методов объекта специальный вызванный метод __iter__, который сообщает ему, что делать. Файловые объекты определяют этот специальный метод для возврата итератора по строкам. (Примерно.)
Катриэль
130

Два эффективных по памяти способа в ранжированном порядке (первый - лучший) -

  1. использование with - поддерживается с Python 2.5 и выше
  2. использование, yieldесли вы действительно хотите контролировать, сколько читать

1. использование with

withхороший и эффективный способ чтения больших файлов. Преимущества - 1) файловый объект автоматически закрывается после выхода из withблока исполнения. 2) обработка исключений внутри withблока. 3) forцикл памяти перебирает fфайловый объект построчно. внутренне он выполняет буферизованный ввод-вывод (для оптимизации дорогостоящих операций ввода-вывода) и управление памятью.

with open("x.txt") as f:
    for line in f:
        do something with data

2. использование yield

Иногда может потребоваться более детальный контроль над тем, сколько читать в каждой итерации. В этом случае используйте iter & yield . Обратите внимание, что при использовании этого метода необходимо закрыть файл в конце.

def readInChunks(fileObj, chunkSize=2048):
    """
    Lazy function to read a file piece by piece.
    Default chunk size: 2kB.
    """
    while True:
        data = fileObj.read(chunkSize)
        if not data:
            break
        yield data

f = open('bigFile')
for chuck in readInChunks(f):
    do_something(chunk)
f.close()

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

В Python наиболее распространенный способ чтения строк из файла заключается в следующем:

for line in open('myfile','r').readlines():
    do_something(line)

Когда это сделано, readlines()функция (то же самое относится и к read()функции) загружает весь файл в память, а затем перебирает его. Немного лучший подход (первые два упомянутых метода являются лучшими) для больших файлов заключается в использовании fileinputмодуля следующим образом:

import fileinput

for line in fileinput.input(['myfile']):
    do_something(line)

fileinput.input()вызов читает строку последовательно, но не сохраняет их в памяти после того, как они были считаны или даже просто так это, так как fileв питоной итерации.

Ссылки

  1. Python с заявлением
Срикар Аппалараджу
источник
9
-1 В принципе, это никогда не хорошая идея for line in open(...).readlines(): <do stuff>. Почему ты?! Вы только что потеряли все преимущества умного буферизированного ввода-вывода Python без какой-либо выгоды.
Катриэль
5
@Srikar: есть время и место для предоставления всех возможных решений проблемы; учить новичка, как делать ввод файлов, не так ли. Наличие правильного ответа в конце длинного поста, заполненного неправильными ответами, не является хорошим обучением.
Катриэль
6
@Srikar: Вы могли бы сделать свой пост значительно лучше, поставив правильный путь вверху, затем упомянув readlinesи объяснив, почему это нехорошо (потому что он читает файл в память), затем объяснив, что fileinputделает модуль и почему вы Возможно, вы захотите использовать его поверх других методов, затем объясните, как разбиение файла на блоки улучшает работу ввода-вывода, и приведите пример функции разбиения на блоки (но упомяните, что Python уже делает это для вас, так что вам это не нужно). Но просто дать пять способов решить простую проблему, четыре из которых в данном случае неверны, нехорошо.
Катриэль
2
Что бы вы ни добавляли ради полноты, добавьте это в последнюю очередь, а не в первую очередь. Сначала покажи правильный путь.
m000
6
@katrielalex пересмотрел мой ответ и обнаружил, что он требует реструктуризации. Я вижу, как более ранний ответ может вызвать путаницу. Надеюсь, это станет понятным для будущих пользователей.
Srikar Appalaraju
37

Для удаления новых строк:

with open(file_path, 'rU') as f:
    for line_terminated in f:
        line = line_terminated.rstrip('\n')
        ...

С универсальной поддержкой новой строки все строки файла текст будет казаться завершаться '\n', независимо терминаторы в файле, '\r', '\n'или '\r\n'.

РЕДАКТИРОВАТЬ - Чтобы указать универсальную поддержку новой строки:

  • Python 2 в Unix open(file_path, mode='rU')- требуется [спасибо @Dave ]
  • Python 2 в Windows - open(file_path, mode='rU')- необязательно
  • Python 3 open(file_path, newline=None)- необязательно

newlineПараметр поддерживается только в Python 3 и по умолчанию None. В modeпараметрах по умолчанию 'r'во всех случаях. UОсуждается в Python 3. В Python 2 на Windows , другой механизм , как представляется , перевести \r\nв \n.

Docs:

Для сохранения собственных терминаторов строки:

with open(file_path, 'rb') as f:
    with line_native_terminated in f:
        ...

Двоичный режим все еще может анализировать файл на строки с in. Каждая строка будет иметь те терминаторы, которые есть в файле.

Благодаря @katrielalex «s ответ , Python, открытый () документ, и IPython эксперименты.

Боб Стейн
источник
1
На Python 2.7 мне пришлось open(file_path, 'rU')включить универсальные переводы строк.
Дейв
17

это возможный способ чтения файла в Python:

f = open(input_file)
for line in f:
    do_stuff(line)
f.close()

он не выделяет полный список. Он перебирает строки.

Саймон Бергот
источник
2
Хотя это работает, это определенно не канонический путь. Каноническим способом является использование контекстной обертки, например with open(input_file) as f:. Это спасет вас f.close()и гарантирует, что вы случайно не забудете закрыть его. Предотвращает утечки памяти и все, что очень важно при чтении файлов.
Мач
1
Как сказал @Mast, это не канонический путь, так что понизьте это.
azuax
12

Немного контекста заранее о том, откуда я иду. Фрагменты кода в конце.

Когда я могу, я предпочитаю использовать инструмент с открытым исходным кодом, такой как H2O, для высокопроизводительного параллельного чтения файлов CSV, но этот инструмент ограничен в наборе функций. Я заканчиваю тем, что пишу много кода для создания конвейеров данных, прежде чем подавать его в кластер H2O для обучения под наблюдением.

Я читал файлы, такие как набор данных HIGGS 8 ГБ из репозитория UCI и даже файлы CSV 40 ГБ для целей обработки данных, значительно быстрее, добавляя много параллелизма с помощью объекта пула и карты функции многопроцессорной библиотеки. Например, кластеризация с поиском ближайшего соседа, а также алгоритмами кластеризации DBSCAN и Маркова требует некоторого изощренного параллельного программирования, чтобы обойти некоторые серьезные проблемы с памятью и временем настенных часов.

Я обычно люблю разбивать файл по строкам на части, используя инструменты gnu, а затем glob-filemask их все, чтобы найти и прочитать их параллельно в программе python. Я обычно использую что-то вроде 1000+ частичных файлов. Выполнение этих приемов очень помогает в скорости обработки и ограничении памяти.

Pandas dataframe.read_csv является однопоточным, поэтому вы можете сделать эти трюки, чтобы сделать панды более быстрыми, запустив map () для параллельного выполнения. Вы можете использовать htop, чтобы увидеть, что с обычными старыми последовательными пандами dataframe.read_csv 100% ЦП только на одном ядре - это фактическое узкое место в pd.read_csv, а не на диске вообще.

Я должен добавить, что я использую SSD на быстрой шине видеокарты, а не вращающийся HD на шине SATA6, плюс 16 процессорных ядер.

Кроме того, другой метод, который я обнаружил, прекрасно работает в некоторых приложениях - это параллельное чтение файла CSV в одном гигантском файле, начиная с каждого рабочего с разным смещением в файле, вместо предварительного разделения одного большого файла на множество файлов деталей. Используйте Python для поиска файлов () и Tell () в каждом параллельном работнике, чтобы читать большой текстовый файл в виде полос, с разными байтовыми смещениями начальных и конечных байтов в большом файле, одновременно в одно и то же время. Вы можете выполнить поиск по всем регулярным выражениям в байтах и ​​вернуть счетчик перевода строки. Это частичная сумма. Наконец, суммируйте частичные суммы, чтобы получить глобальную сумму, когда функция карты возвращается после того, как рабочие закончили.

Ниже приведены некоторые примеры тестов с использованием трюка с параллельным байтовым смещением:

Я использую 2 файла: HIGGS.csv составляет 8 ГБ. Это из хранилища машинного обучения UCI. all_bin .csv имеет размер 40,4 ГБ и является результатом моего текущего проекта. Я использую 2 программы: GNU wc, которая поставляется вместе с Linux, и программу на python fastread.py, которую я разработал.

HP-Z820:/mnt/fastssd/fast_file_reader$ ls -l /mnt/fastssd/nzv/HIGGS.csv
-rw-rw-r-- 1 8035497980 Jan 24 16:00 /mnt/fastssd/nzv/HIGGS.csv

HP-Z820:/mnt/fastssd$ ls -l all_bin.csv
-rw-rw-r-- 1 40412077758 Feb  2 09:00 all_bin.csv

ga@ga-HP-Z820:/mnt/fastssd$ time python fastread.py --fileName="all_bin.csv" --numProcesses=32 --balanceFactor=2
2367496

real    0m8.920s
user    1m30.056s
sys 2m38.744s

In [1]: 40412077758. / 8.92
Out[1]: 4530501990.807175

Это примерно 4,5 ГБ / с, или 45 ГБ / с, скорость записи файла. Это не вращающийся жесткий диск, мой друг. Это на самом деле Samsung Pro 950 SSD.

Ниже приведен тест скорости для того же файла, который был посчитан строкой gnu wc, скомпилированной программой на чистом C.

Круто то, что вы можете видеть, что моя программа на чистом Python в этом случае практически соответствовала скорости скомпилированной программы gnu wc. Python интерпретируется, но C компилируется, так что это довольно интересный подвиг скорости, я думаю, вы согласитесь. Конечно, wc действительно нужно заменить на параллельную программу, и тогда он действительно побьет все мои программы на python. Но в нынешнем виде gnu wc - это просто последовательная программа. Вы делаете то, что можете, и Python может делать параллельное сегодня. Компиляция Cython может помочь мне (в другое время). Также отображенные в память файлы еще не исследовались.

HP-Z820:/mnt/fastssd$ time wc -l all_bin.csv
2367496 all_bin.csv

real    0m8.807s
user    0m1.168s
sys 0m7.636s


HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.257s
user    0m12.088s
sys 0m20.512s

HP-Z820:/mnt/fastssd/fast_file_reader$ time wc -l HIGGS.csv
11000000 HIGGS.csv

real    0m1.820s
user    0m0.364s
sys 0m1.456s

Вывод: скорость хороша для чистой программы на Python по сравнению с программой на Си. Тем не менее, недостаточно использовать чистую программу на Python поверх программы на C, по крайней мере, для целей линейного монтажа. Обычно этот метод может использоваться для обработки других файлов, поэтому этот код на Python все еще хорош.

Вопрос: Будет ли компиляция регулярного выражения только один раз и передача его всем работникам улучшать скорость? Ответ: Предварительная компиляция Regex НЕ помогает в этом приложении. Я предполагаю, что причина в том, что накладные расходы на сериализацию и создание процессов для всех работников являются доминирующими.

Еще кое-что. Параллельное чтение файла CSV даже помогает? Является ли диск узким местом или процессором? Многие так называемые ответы с самым высоким рейтингом в stackoverflow содержат общую мудрость разработчиков, согласно которой вам нужен только один поток для чтения файла, говорят они, лучше всего. Хотя они уверены?

Давайте выясним:

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.256s
user    0m10.696s
sys 0m19.952s

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=1
11000000

real    0m17.380s
user    0m11.124s
sys 0m6.272s

Ах да, да, это так. Параллельное чтение файлов работает довольно хорошо. Ну вот и все!

Ps. В случае, если некоторые из вас хотят знать, что если balanceFactor равнялся 2 при использовании одного рабочего процесса? Ну это ужасно

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=2
11000000

real    1m37.077s
user    0m12.432s
sys 1m24.700s

Ключевые части программы python fastread.py:

fileBytes = stat(fileName).st_size  # Read quickly from OS how many bytes are in a text file
startByte, endByte = PartitionDataToWorkers(workers=numProcesses, items=fileBytes, balanceFactor=balanceFactor)
p = Pool(numProcesses)
partialSum = p.starmap(ReadFileSegment, zip(startByte, endByte, repeat(fileName))) # startByte is already a list. fileName is made into a same-length list of duplicates values.
globalSum = sum(partialSum)
print(globalSum)


def ReadFileSegment(startByte, endByte, fileName, searchChar='\n'):  # counts number of searchChar appearing in the byte range
    with open(fileName, 'r') as f:
        f.seek(startByte-1)  # seek is initially at byte 0 and then moves forward the specified amount, so seek(5) points at the 6th byte.
        bytes = f.read(endByte - startByte + 1)
        cnt = len(re.findall(searchChar, bytes)) # findall with implicit compiling runs just as fast here as re.compile once + re.finditer many times.
    return cnt

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

Спасибо: Проект H2O с открытым исходным кодом, разработанный Арно и Клиффом и сотрудниками H2O за их великолепное программное обеспечение и обучающие видео, которые вдохновили меня на этот высокопроизводительный параллельный считыватель байтовых смещений на python, как показано выше. H2O выполняет параллельное чтение файлов с использованием Java, может вызываться программами на Python и R и быстро сходит с ума, быстрее, чем что-либо на планете, при чтении больших файлов CSV.

Джеффри Андерсон
источник
Параллельные куски - вот что это, в основном. Кроме того, я ожидаю, что SSD и Flash - единственные совместимые устройства хранения данных с этой техникой. Spinning HD вряд ли будет совместимым.
Джеффри Андерсон
1
Как вы учли ОС для кеширования дисковых файлов?
JamesThomasMoon1979
5

Katrielalex предоставил способ открыть и прочитать один файл.

Однако, как работает ваш алгоритм, он читает весь файл для каждой строки файла. Это означает, что общий объем чтения файла и вычисления расстояния Левенштейна будет выполнен N * N, если N - количество строк в файле. Поскольку вас беспокоит размер файла, и вы не хотите хранить его в памяти, меня беспокоит результирующее квадратичное время выполнения . Ваш алгоритм относится к классу алгоритмов O (n ^ 2), которые часто можно улучшить с помощью специализации.

Я подозреваю, что вы уже знаете соотношение между памятью и временем выполнения здесь, но, возможно, вы захотите выяснить, существует ли эффективный способ вычисления нескольких расстояний Левенштейна параллельно. Если так, то было бы интересно поделиться своим решением здесь.

Сколько строк в ваших файлах и на каком компьютере (мощность mem & cpu) должен работать ваш алгоритм, и каково допустимое время выполнения?

Код будет выглядеть так:

with f_outer as open(input_file, 'r'):
    for line_outer in f_outer:
        with f_inner as open(input_file, 'r'):
            for line_inner in f_inner:
                compute_distance(line_outer, line_inner)

Но вопрос в том, как вы сохраняете расстояния (матрицу?) И можете ли вы получить преимущество от подготовки, например, external_line для обработки, или кэширования некоторых промежуточных результатов для повторного использования.

CFI
источник
Я хочу сказать, что этот пост не содержит ответа на вопрос, просто еще несколько вопросов! ИМО было бы лучше подходить в качестве комментария.
Катриэль
1
@katriealex: Err. Странный. Вы видели вложенные циклы, расширяя свой ответ, чтобы соответствовать актуальному вопросу? Я могу удалить свои вопросы здесь из своего ответа, и пока еще достаточно контента, чтобы гарантировать его, хотя и частичный ответ. Я также мог бы согласиться, если бы вы отредактировали свой собственный ответ, включив в него пример вложенного цикла - который был явно задан вопросом - и тогда я мог бы счастливо удалить свой собственный ответ. Но понизить голос - это то, чего я совсем не понимаю.
CFI
Справедливо; На самом деле я не вижу демонстрации вложенных циклов for в качестве ответа на вопрос, но думаю, что он довольно сильно ориентирован на новичков. Downvote удален.
Катриэль
3
#Using a text file for the example
with open("yourFile.txt","r") as f:
    text = f.readlines()
for line in text:
    print line
  • Откройте файл для чтения (r)
  • Прочитайте весь файл и сохраните каждую строку в список (текст)
  • Прокручивайте список, печатая каждую строку.

Если вы хотите, например, проверить определенную строку на длину более 10, работайте с тем, что у вас уже есть.

for line in text:
    if len(line) > 10:
        print line
loxsat
источник
1
Не самый лучший ответ на этот вопрос, но этот код в основном полезен в случае, если вы ищете .is "slurping" (чтение всего файла сразу). Это был мой случай, и Google получил меня здесь. +1. Кроме того, для атомарности, или если вы занимаете много времени обработка в цикле может закончиться быстрее, чтобы прочитать весь файл
нтг
1
Кроме того, немного улучшен код: 1. close не требуется после с помощью: ( docs.python.org/2/tutorial/inputoutput.html , поиск по запросу «Рекомендуется использовать ключевое слово with ...») 2 . текст может быть обработан после чтения файла (в противном случае с циклом ....)
нтг
2

Из документации на python для fileinput .input ():

Это перебирает строки всех файлов, перечисленных в sys.argv[1:], по умолчанию, sys.stdinесли список пуст

далее, определение функции:

fileinput.FileInput([files[, inplace[, backup[, mode[, openhook]]]]])

читая между строк, это говорит мне, что это filesможет быть список, чтобы вы могли иметь что-то вроде:

for each_line in fileinput.input([input_file, input_file]):
  do_something(each_line)

Смотрите здесь для получения дополнительной информации

KevinDTimm
источник
2

Я настоятельно рекомендую не использовать загрузку файлов по умолчанию, так как это ужасно медленно. Вы должны посмотреть на numpy функции и функции IOpro (например, numpy.loadtxt ()).

http://docs.scipy.org/doc/numpy/user/basics.io.genfromtxt.html

https://store.continuum.io/cshop/iopro/

Затем вы можете разбить вашу попарную операцию на куски:

import numpy as np
import math

lines_total = n    
similarity = np.zeros(n,n)
lines_per_chunk = m
n_chunks = math.ceil(float(n)/m)
for i in xrange(n_chunks):
    for j in xrange(n_chunks):
        chunk_i = (function of your choice to read lines i*lines_per_chunk to (i+1)*lines_per_chunk)
        chunk_j = (function of your choice to read lines j*lines_per_chunk to (j+1)*lines_per_chunk)
        similarity[i*lines_per_chunk:(i+1)*lines_per_chunk,
                   j*lines_per_chunk:(j+1)*lines_per_chunk] = fast_operation(chunk_i, chunk_j) 

Почти всегда гораздо быстрее загружать данные порциями, а затем выполнять над ними матричные операции, чем делать их поэлементно !!

Джон Хаберстро
источник
0

Нужно часто читать большой файл из последней позиции чтения?

Я создал скрипт, используемый для резки файла Apache access.log несколько раз в день. Поэтому мне нужно было установить курсор на последнюю строку, проанализированную во время последнего выполнения . Для этого я использовал file.seek()и file.seek()методы, позволяющие хранить курсор в файле.

Мой код:

ENCODING = "utf8"
CURRENT_FILE_DIR = os.path.dirname(os.path.abspath(__file__))

# This file is used to store the last cursor position
cursor_position = os.path.join(CURRENT_FILE_DIR, "access_cursor_position.log")

# Log file with new lines
log_file_to_cut = os.path.join(CURRENT_FILE_DIR, "access.log")
cut_file = os.path.join(CURRENT_FILE_DIR, "cut_access", "cut.log")

# Set in from_line 
from_position = 0
try:
    with open(cursor_position, "r", encoding=ENCODING) as f:
        from_position = int(f.read())
except Exception as e:
    pass

# We read log_file_to_cut to put new lines in cut_file
with open(log_file_to_cut, "r", encoding=ENCODING) as f:
    with open(cut_file, "w", encoding=ENCODING) as fw:
        # We set cursor to the last position used (during last run of script)
        f.seek(from_position)
        for line in f:
            fw.write("%s" % (line))

    # We save the last position of cursor for next usage
    with open(cursor_position, "w", encoding=ENCODING) as fw:
        fw.write(str(f.tell()))
Сэмюэль Даузон
источник
-2

Лучший способ прочитать большой файл, строка за строкой - использовать функцию перечисления Python

with open(file_name, "rU") as read_file:
    for i, row in enumerate(read_file, 1):
        #do something
        #i in line of that line
        #row containts all data of that line
Анураг Мисра
источник
3
Почему использование перечисления лучше? Единственное преимущество перед принятым ответом состоит в том, что вы получаете индекс, который не нужен OP, и вы делаете код менее читабельным.
fuyas