2

Имеются большие файлы от 5мб и выше, к примеру 1 файл содержит 114320 строк.

Необходимо считать последнюю или предпоследнюю строку (если в последней был символ новой строки), и дописать новые данные. Видел разные реализации, но все они предполагают, что файл будет маленький.

insolor
  • 49,104
Depish
  • 961
  • 1
  • 1
    5MB или 100K строк это относительно маленькие данные: начните с самого простого кода, который работает в вашем случае. – jfs Aug 28 '17 at 04:04
  • @jfs если использовать цикл который переходит по строкам то это занимает непростительно много времени, необходимо что-то побыстрее, есть такое? можно ли как-то с конца читать фаил? можно было бы наткнуться на символ переноса строки и понять что последнюю строку уже нашли – Depish Aug 28 '17 at 04:06
  • 5
    Попробуйте на ссылку нажать – jfs Aug 28 '17 at 04:09

1 Answers1

6

Чтобы прочитать последнюю строку в относительно небольшом файле:

from collections import deque

with open('файл.txt') as file:
     [last_line] = deque(file, maxlen=1) or ['']

Большой файл можно с конца читать пока новая строка не встретится, используя mmap:

from mmap import mmap, ACCESS_READ

def last_line(filename, newline=b'\n'):
    with open(filename, 'rb', 0) as file:
        try:
            s = mmap(file.fileno(), 0, access=ACCESS_READ)
        except ValueError:  # empty
            return b''
        else:
            with s:
                i = s.rfind(newline, 0, -1)
                return s[i + len(newline) if i != -1 else 0:]

Этот вариант возвращает байты и не поддерживает режим универсальных строк. Пример:

>>> last_line('no newline.txt')
b'abc'

Для чтения пары строк с конца, ещё может подойти, к примеру, tail(file, n) функция из @A. Coady ответа. Размер буфера в ответе удваивается по схожей причине, описанной в Непонятное замедление конкатенации в цикле — при чтении с конца нужно быть осторожным, чтобы не сделать ваш алгоритм квадратичным случайно:

def last_line(filename, newline=b'\n'):
    with open(filename, 'rb') as file:
        size, s = 2, b''
        while s.find(newline, 0, -1) == -1:  # until a full line is read
            try:
                file.seek(-size, os.SEEK_END)
            except IOError:  # file is too small
                file.seek(0)  # rewind to beginning
                break
            finally:
                s = file.read(size)
            size *= 2
        i = s.rfind(newline, 0, -1)
        return s[i + len(newline) if i != -1 else 0:]

Сравните производительность (включая deque() вариант), чтобы узнать какое решение лучше работает в вашем случае.

jfs
  • 52,361
  • А прямо по адресу в файле читать блоки по 4К с конца и искать в них \n в Python сложно? – avp Aug 28 '17 at 18:25
  • @avp самый популярный ответ по ссылке уже по блокам читает (это относительно сложный вариант. last_line(), реализованная по идеям tail() из A. Coady ответа, — проще). – jfs Aug 28 '17 at 18:49
  • На Си я бы возвращал из подобной функции позицию и длину. Если содержимое реально потребуется, то вызывающая сторона может сама прочесть его в реально существующую у нее область памяти. Но, конечно, это уже все из области "философских" вопросов разработки. – avp Aug 28 '17 at 21:13
  • @avp: в Питоне функция last_line() возвращает последнюю строку, вместо позиции последней строки в файле :) Последнее обновление показывает ещё одно достоинством методов в ответе: легко добавить поддержку многобайтных разделителей строк (в варианте с блоками пришлось бы ещё больше усложнить код, чтобы правильно разделитель, попавший на границу между блоками отловить). – jfs Aug 29 '17 at 09:33
  • И вы на практике встречали такие разделители? (очевидно, что \n в кодировках UTF-16 и UCS-32 не будет разрезан) – avp Aug 29 '17 at 17:53
  • @avp конечно, len(os.linesep) > 1 на Windows. Бывает полезно свои записи иметь, к примеру, чтобы вывод shell по prompt разделить (функциональность в pexpect можно встретить). See also, Add support for reading records with arbitrary separators to the standard IO stack – jfs Aug 29 '17 at 18:19
  • В самом деле, есть такое. Я даже когда-то делал это в своей версии getline(), но уже очень давно не использовал. Согласен, эта штука усложнит код чтения сзади-наперед. Придется сохранять состояние между чтениями блоков. Кстати, так из вашей ссылки на Issue1152248 и не понял, 9 лет (с 5-го по 14-й год) разработки чем завершились? Сейчас стандартно file.readlines позволяют задавать произвольный разделитель? – avp Aug 29 '17 at 21:33
  • @avp: смысл ссылки на баг, чтобы увидеть обсуждение мотивации: зачем иметь разделитель отличный от b'\n' и что реализация может быть сложнее чем кажется на первый взгляд. В stdlib я не знаю средств, которые позволяют произвольный разделить указывать. Обычно проще по потребностям подходящую к конкретному случаю реализацию написать или что-то вроде fdpexpect использовать. – jfs Aug 29 '17 at 22:16