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

239

Мне нужно прочитать большой файл, строка за строкой. Допустим, файл имеет более 5 ГБ, и мне нужно прочитать каждую строку, но, очевидно, я не хочу использовать, readlines()потому что это создаст очень большой список в памяти.

Как будет работать код ниже для этого случая? Читает ли xreadlinesсам по себе в память? Нужно ли выражение генератора?

f = (line for line in open("log.txt").xreadlines())  # how much is loaded in memory?

f.next()  

Кроме того, что я могу сделать, чтобы прочитать это в обратном порядке, так же, как команда Linux tail?

Я нашел:

http://code.google.com/p/pytailer/

и

« голова питона, хвост и назад читаются по строкам текстового файла »

Оба работали очень хорошо!

Бруно Роча - Рочакбруно
источник
И что я могу сделать, чтобы прочитать это с хвоста? построчно, начиная с последней строки.
Бруно Роча - Рочакбруно
это должен быть отдельный вопрос
cmcginty
1
дубликат stackoverflow.com/questions/5896079/…
cmcginty

Ответы:

311

Я представил этот ответ , потому что Кит, в то время как сжато, не закрывает файл явно

with open("log.txt") as infile:
    for line in infile:
        do_something_with(line)
Джон Ла Рой
источник
32
вопрос все еще в том, "для строки в infile" будет загружать мои 5 ГБ строк в память? и как я могу читать с хвоста?
Бруно Роча - Рочакбруно
68
@rochacbruno, он читает только одну строку за раз. Когда будет прочитана следующая строка, предыдущая будет собрана сборщиком мусора, если вы не сохранили ссылку на нее где-то еще
John La Rooy
1
@rochacbruno, чтение строк в обратном порядке, к сожалению, не так легко сделать эффективно. Как правило, вы хотите прочитать от конца файла куски разумного размера (скажем, от килобайтов до мегабайт) и разделить на символы новой строки (или любой другой символ конца строки на вашей платформе)
John La Rooy
4
Спасибо! Я нашел решение для хвоста stackoverflow.com/questions/5896079/…
Бруно Роша - rochacbruno
1
@bawejakunal, Вы имеете в виду, если строка слишком длинная для загрузки в память сразу? Это необычно для текстового файла. Вместо использования forцикла, который перебирает строки, вы можете использовать chunk = infile.read(chunksize)для чтения фрагментов ограниченного размера независимо от их содержимого. Вам придется искать внутри фрагментов новые строки самостоятельно.
Джон Ля Рой
60

Все, что вам нужно сделать, это использовать объект файла в качестве итератора.

for line in open("log.txt"):
    do_something_with(line)

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

with open("log.txt") as fileobject:
    for line in fileobject:
        do_something_with(line)

Это также автоматически закроет файл.

Кит
источник
2
Это не загружает весь файл в память?
Бруно Роча - Рочакбруно
17

Старый школьный подход:

fh = open(file_name, 'rt')
line = fh.readline()
while line:
    # do stuff with line
    line = fh.readline()
fh.close()
PTBNL
источник
2
Небольшое замечание: для безопасности исключений рекомендуется использовать выражение «с», в вашем случае «с открытым (имя файла,« rt ») как fh:»
prokher
16
@prokher: Да, но я назвал это "старой школой".
PTBNL
15

Вместо этого вам лучше использовать итератор. Соответствующий: http://docs.python.org/library/fileinput.html

Из документов:

import fileinput
for line in fileinput.input("filename"):
    process(line)

Это позволит избежать копирования всего файла в память сразу.

Микола
источник
Хотя документы показывают фрагмент как «типичное использование», его использование не вызывает close()метод возвращенного FileInputобъекта класса по окончании цикла - поэтому я бы не стал использовать его таким образом. В Python 3.2 они наконец сделали fileinputсовместимым с протоколом менеджера контекста, который решает эту проблему (но код все равно не был бы написан так, как показано).
Мартино
7

Вот что вы делаете, если в файле нет новых строк:

with open('large_text.txt') as f:
  while True:
    c = f.read(1024)
    if not c:
      break
    print(c)
Ариэль Кабиб
источник
Хотя мне нравится этот метод, вы рискуете разбить строку в тексте на куски. Я видел это лично, что означает, что если вы ищете sstring в файле, как я, я бы пропустил некоторые из них, потому что строка, в которой они находились, была разбита на куски. Есть ли способ обойти это? Использование readlines не помогло, так как я получил неправильные счета @Ariel Cabib
edo101
6

Пожалуйста, попробуйте это:

with open('filename','r',buffering=100000) as f:
    for line in f:
        print line
Джоти дас
источник
пожалуйста, объясни?
Nikhil VJ
3
Из официальных документов Python: link Необязательный аргумент буферизации указывает желаемый размер буфера файла: 0 означает небуферизованный, 1 означает буферизацию строки, любое другое положительное значение означает использование буфера (приблизительно) этого размера (в байтах). Отрицательная буферизация означает использование системной настройки по умолчанию, которая обычно буферизуется строкой для tty-устройств и полностью буферизуется для других файлов. Если опущено, используется системное значение по умолчанию
jyoti das
В моем случае спас мой день с файлами ~ 4Гб с двумя обработчиками файлов (один прочитал, другой записал) python завис и теперь все нормально! Спасибо.
Ксельт
@jyotidas Хотя мне нравится этот метод, вы рискуете разбить строку в тексте на куски. Я видел это лично, что означает, что если вы ищете sstring в файле, как я, я бы пропустил некоторые из них, потому что строка, в которой они находились, была разбита на куски. Есть ли способ обойти это? Использование readlines не сработало, так как я получил
неправильные счета
3

Я не мог поверить, что это может быть так просто, как кажется от ответа @ john-la-rooy. Итак, я воссоздал cpкоманду, используя построчное чтение и запись. Это безумно быстро.

#!/usr/bin/env python3.6

import sys

with open(sys.argv[2], 'w') as outfile:
    with open(sys.argv[1]) as infile:
        for line in infile:
            outfile.write(line)
Бруно Броноски
источник
ПРИМЕЧАНИЕ. Поскольку python readlineстандартизирует окончания строк, это имеет побочный эффект при преобразовании документов с окончаниями строк DOS \r\nв конец строк Unix \n. Вся моя причина поиска по этой теме заключалась в том, что мне нужно было преобразовать файл журнала, который получает беспорядок в конце строк (потому что разработчик слепо использовал различные библиотеки .NET). Я был шокирован, обнаружив, что после моего начального теста скорости мне не нужно было возвращаться назад и rstripк строкам. Это было уже прекрасно!
Бруно Броноски
2

За последние 6 лет этот блестящий проект прошел долгий путь. Он имеет простой API, охватывающий полезный набор функций панд.

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

import dask.dataframe as dd

df = dd.read_csv('filename.csv')
df.head(10)  # return first 10 rows
df.tail(10)  # return last 10 rows

# iterate rows
for idx, row in df.iterrows():
    ...

# group by my_field and return mean
df.groupby(df.my_field).value.mean().compute()

# slice by column
df[df.my_field=='XYZ'].compute()
JPP
источник
2

Вот код для загрузки текстовых файлов любого размера без проблем с памятью. Он поддерживает файлы размером в гигабайты

https://gist.github.com/iyvinjose/e6c1cb2821abd5f01fd1b9065cbc759d

скачайте файл data_loading_utils.py и импортируйте его в свой код

использование

import data_loading_utils.py.py
file_name = 'file_name.ext'
CHUNK_SIZE = 1000000


def process_lines(data, eof, file_name):

    # check if end of file reached
    if not eof:
         # process data, data is one single line of the file

    else:
         # end of file reached

data_loading_utils.read_lines_from_file_as_data_chunks(file_name, chunk_size=CHUNK_SIZE, callback=self.process_lines)

Метод process_lines является функцией обратного вызова. Он будет вызываться для всех строк, причем данные параметров представляют одну строку файла за раз.

Вы можете настроить переменную CHUNK_SIZE в зависимости от конфигурации оборудования вашего компьютера.

Айвин Хосе
источник
Хотя мне нравится этот метод, вы рискуете разбить строку в тексте на куски. Я видел это лично, что означает, что если вы ищете sstring в файле, как я, я бы пропустил некоторые из них, потому что строка, в которой они находились, была разбита на куски. Есть ли способ обойти это? Использование readlines не сработало, так как я получил
неправильные счета
0

Как насчет этого? Разделите ваш файл на куски, а затем прочитайте его построчно, потому что когда вы читаете файл, ваша операционная система будет кэшировать следующую строку. Если вы читаете файл построчно, вы неэффективно используете кэшированную информацию.

Вместо этого разделите файл на куски и загрузите весь кусок в память, а затем выполните обработку.

def chunks(file,size=1024):
    while 1:

        startat=fh.tell()
        print startat #file's object current position from the start
        fh.seek(size,1) #offset from current postion -->1
        data=fh.readline()
        yield startat,fh.tell()-startat #doesnt store whole list in memory
        if not data:
            break
if os.path.isfile(fname):
    try:
        fh=open(fname,'rb') 
    except IOError as e: #file --> permission denied
        print "I/O error({0}): {1}".format(e.errno, e.strerror)
    except Exception as e1: #handle other exceptions such as attribute errors
        print "Unexpected error: {0}".format(e1)
    for ele in chunks(fh):
        fh.seek(ele[0])#startat
        data=fh.read(ele[1])#endat
        print data
Арохи Гупта
источник
Это выглядит многообещающе. Это загрузка байтами или строками? Я боюсь, что строки будут разбиты, если они будут байтами .. как мы можем загрузить, скажем, 1000 строк за раз и обработать это?
Nikhil VJ
0

Спасибо! Недавно я перешел на Python 3 и был разочарован использованием readlines (0) для чтения больших файлов. Это решило проблему. Но чтобы получить каждую строчку, мне пришлось сделать пару дополнительных шагов. Каждой строке предшествовала буква «b», которая, я думаю, была в двоичном формате. Использование «decode (utf-8)» изменило его ascii.

Затем мне пришлось удалить «= \ n» в середине каждой строки.

Затем я разбил строки на новой строке.

b_data=(fh.read(ele[1]))#endat This is one chunk of ascii data in binary format
        a_data=((binascii.b2a_qp(b_data)).decode('utf-8')) #Data chunk in 'split' ascii format
        data_chunk = (a_data.replace('=\n','').strip()) #Splitting characters removed
        data_list = data_chunk.split('\n')  #List containing lines in chunk
        #print(data_list,'\n')
        #time.sleep(1)
        for j in range(len(data_list)): #iterate through data_list to get each item 
            i += 1
            line_of_data = data_list[j]
            print(line_of_data)

Вот код, начинающийся чуть выше «данных печати» в коде Арохи.

Джон Хейнс
источник
0

Я продемонстрировал подход произвольного доступа с параллельным байтовым уровнем здесь в этом другом вопросе:

Получение количества строк в текстовом файле без readlines

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

Джеффри Андерсон
источник
0

Лучшее решение, которое я нашел в отношении этого, и я попробовал это на 330 МБ файла.

lineno = 500
line_length = 8
with open('catfour.txt', 'r') as file:
    file.seek(lineno * (line_length + 2))
    print(file.readline(), end='')

Где line_length - это количество символов в одной строке. Например, «abcd» имеет длину строки 4.

Я добавил 2 в длину строки, чтобы пропустить символ '\ n' и перейти к следующему символу.

Али Саджад
источник
-1

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

def readInChunks(fileObj, chunkSize=1024):
    while True:
        data = fileObj.read(chunkSize)
        if not data:
            break
        while data[-1:] != '\n':
            data+=fileObj.read(1)
        yield data
Адам
источник
-10
f=open('filename','r').read()
f1=f.split('\n')
for i in range (len(f1)):
    do_something_with(f1[i])

надеюсь это поможет.

Сайник Кр Махата
источник
5
Разве это не прочитало бы весь файл в памяти? Вопрос прямо спрашивает, как этого избежать, поэтому это не отвечает на вопрос.
Парадокс Ферми