Как читать последний МБ очень большого текстового файла

Я пытаюсь найти строку в конце текстового файла. Проблема в том, что текстовый файл может сильно различаться по размеру. От 3 МБ до 4 ГБ. Но каждый раз, когда я пытаюсь запустить script, чтобы найти эту строку в текстовом файле, который составляет около 3 ГБ, на моем компьютере заканчивается память. SO Мне было интересно, если бы все-таки было python, чтобы найти размер файла, а затем прочитать последний мегабайт.

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

find_str = "ERROR"
file = open(file_directory)                           
last_few_lines​ = file.readlines()[-20:]   

error​ = False  

for line in ​last_few_lines​:
    if find_str in line:
    ​    error​ = True

Ответ 1

Используйте file.seek():

import os
find_str = "ERROR"
error = False
# Open file with 'b' to specify binary mode
with open(file_directory, 'rb') as file:
    file.seek(-1024 * 1024, os.SEEK_END)  # Note minus sign
    if find_str in file.read():
        error = True

Вы должны указать двоичный режим, когда вы открываете файл, или вы получите поведение "undefined". В python2 он может работать в любом случае (это было для меня), но в python3 seek() будет возникать исключение io.UnsupportedOperation, если файл был открыт в текстовом режиме по умолчанию. Документы python 3 здесь. Хотя из этих документов неясно, константы SEEK_* все еще находятся в модуле os.

Обновление: Использование with statement для более безопасного управления ресурсами, как это предложил Крис Бетти.

Ответ 2

Вы можете использовать хвост рецепта с deque, чтобы получить последние строки n большого файла:

from collections import deque

def tail(fn, n):
    with open(fn) as fin:
        return list(deque(fin, n))

Теперь проверьте это.

Сначала создайте большой файл:

>>> with open('/tmp/lines.txt', 'w') as f:
...    for i in range(1,10000000+1):
...       print >> f, 'Line {}'.format(i)  # Python 3: print('Line {}'.format(i), file=f)

# about 128 MB on my machine

Затем проверьте:

print tail('/tmp/lines.txt', 20) 
# ['Line 9999981\n', 'Line 9999982\n', 'Line 9999983\n', 'Line 9999984\n', 'Line 9999985\n', 'Line 9999986\n', 'Line 9999987\n', 'Line 9999988\n', 'Line 9999989\n', 'Line 9999990\n', 'Line 9999991\n', 'Line 9999992\n', 'Line 9999993\n', 'Line 9999994\n', 'Line 9999995\n', 'Line 9999996\n', 'Line 9999997\n', 'Line 9999998\n', 'Line 9999999\n', 'Line 10000000\n']

Это вернет последние n строк, а не последние X байтов файла. Размер данных совпадает с размером строк, а не размером файла. Объект fin используется как итератор по строкам файла, поэтому весь файл не постоянно находится в памяти.

Ответ 3

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

for line in file(file_directory):
    if find_str in line:
        error = True

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