Как читать записи, завершенные пользовательским разделителем из файла в python?

Мне нужен способ сделать for line in file в python, где конец строки переопределяется как любая строка, которую я хочу. Другой способ сказать, что я хочу читать записи из файла, а не строки; Я хочу, чтобы это было так же быстро и удобно делать, как чтение строк.

Это эквивалент python для установки разделителя входных записей $/ в Perl или использования Scanner в java. Это не обязательно должно использовать for line in file (в частности, итератор может не быть файловым объектом). Просто что-то эквивалентное, что позволяет избежать считывания слишком большого количества данных в память.

См. также: Добавить поддержку чтения записей с произвольными разделителями в стандартный стек ввода-вывода


person Alex I    schedule 25.10.2013    source источник


Ответы (2)


В объекте file Python 2.x или в классах io Python 3.3 нет ничего, что позволяло бы указать пользовательский разделитель для readline. (В конечном итоге for line in file использует тот же код, что и readline.)

Но его довольно легко построить самостоятельно. Например:

def delimited(file, delimiter='\n', bufsize=4096):
    buf = ''
    while True:
        newbuf = file.read(bufsize)
        if not newbuf:
            yield buf
            return
        buf += newbuf
        lines = buf.split(delimiter)
        for line in lines[:-1]:
            yield line
        buf = lines[-1]

Вот глупый пример этого в действии:

>>> s = io.StringIO('abcZZZdefZZZghiZZZjklZZZmnoZZZpqr')
>>> d = delimited(s, 'ZZZ', bufsize=2)
>>> list(d)
['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr']

Если вы хотите сделать это правильно как для двоичных, так и для текстовых файлов, особенно в 3.x, это немного сложнее. Но если это должно работать только для одного или другого (и одного языка или другого), вы можете игнорировать это.

Аналогично, если вы используете Python 3.x (или используете объекты io в Python 2.x) и хотите использовать буферы, которые уже поддерживаются в BufferedIOBase, вместо того, чтобы просто поместить буфер поверх буфера , это сложнее. Документы io объясняют, как делать все… но я не знаю простых примеров, поэтому вам действительно придется прочитать хотя бы половину этой страницы и просмотреть остальную часть. (Конечно, вы можете просто использовать необработанные файлы напрямую... но не в том случае, если вы хотите найти разделители Юникода...)

person abarnert    schedule 25.10.2013
comment
После прочтения всей проблемы с трекером, на которую ссылается OP, похоже, что Дуглас Алан уже опубликовал очень похожий рецепт 5 лет в обсуждение. Мне он нравится больше, потому что он позволяет вам преобразовать входную новую строку в выходную новую строку, а не просто отбрасывать ее… но вместо того, чтобы редактировать мою, чтобы она соответствовала, я просто оставлю ссылку. - person abarnert; 26.10.2013
comment
Еще одним преимуществом связанного является то, что он возвращает остаток буфера, когда поток закрывается. - person jozxyqk; 31.03.2015
comment
@jozxyqk: я не уверен, что ты имеешь в виду. Эта версия дает оставшуюся часть буфера в EOF. (Если файл на самом деле закрыт от вас и вызвал исключение, я предполагаю, что вы хотите это исключение - в конце концов, весь смысл в том, чтобы работать как для строки в файле: но с другим разделителем.) - person abarnert; 01.04.2015
comment
Ааа, моя ошибка, надо было внимательнее читать. Я тестировал, читая sys.stdin напрямую и печатая вывод, все еще используя \n, и по какой-то причине оставшиеся символы не печатались, когда я нажимал Ctrl-D. Глядя на код еще раз, я не уверен, почему, и предполагаю, что сделал что-то не так. - person jozxyqk; 01.04.2015
comment
@jozxyqk: Чтение из стандартного ввода с линейной буферизацией имеет некоторые странности с ^D, которые зависят от вашей платформы, терминала и версии Python, что может помешать тестированию других вещей. (Посмотрите, если для строки в sys.stdin(): и для строки в iter(input, ''): делают разные вещи для вас...) - person abarnert; 02.04.2015

Ссылка на обсуждение проблемы OP содержит еще одно решение для чтения строк данных, заканчивающихся настраиваемым разделителем из файл, отправленный Аланом Барнетом. Он работает как с текстовыми, так и с бинарными файлами и является большим улучшением рецепта fileLineIter Дугласа Алана.

Вот моя отполированная версия resplit Алана Барнета. Я заменил добавление строки += на предположительно более быструю "".join конкатенацию строк и я добавил подсказки типов для еще большей производительности. Моя версия настроена на работу с бинарными файлами. Я должен использовать шаблон регулярного выражения для разделения, потому что мой разделитель в его простой форме также встречается внутри строк данных в функции без разделителя, поэтому мне нужно учитывать его контекст. Однако вы можете перенастроить его для текстовых файлов и заменить шаблон регулярного выражения обычным str, если у вас есть простой и уникальный разделитель, который больше нигде не используется.

import pathlib
import functools
import re
from typing import Iterator, Iterable, ByteString
import logging

logging.basicConfig(level=logging.DEBUG)
logging.getLogger().setLevel(logging.DEBUG)
logger = logging.getLogger(__name__)


def resplit(chunks_of_a_file: Iterator, split_pattern: re.Pattern) -> Iterable[ByteString]:
    """
    Reads chunks of a file one chunk at a time, 
    splits them into data rows by `split_pattern` 
    and joins partial data rows across chunk boundaries.
    borrowed from https://bugs.python.org/issue1152248#msg223491
    """
    partial_line = None
    for chunk in chunks_of_a_file:
        if partial_line:
            partial_line = b"".join((partial_line, chunk))
        else:
            partial_line = chunk
        if not chunk:
            break
        lines = split_pattern.split(partial_line)
        partial_line = lines.pop()
        yield from lines
    if partial_line:
        yield partial_line


if __name__ == "__main__":
    path_to_source_file = pathlib.Path("source.bin")
    with open(path_to_source_file, mode="rb") as file_descriptor:
        buffer_size = 8192
        sentinel = b""
        chunks = iter(functools.partial(file_descriptor.read, buffer_size), sentinel)
        data_rows_delimiter = re.compile(b"ABC")
        lines = resplit(chunks, data_rows_delimiter)
        for line in lines:
            logger.debug(line)
person Zababa    schedule 18.09.2019