Почему .read() вызывает перемещение указателя файла в неожиданные позиции?

Вопрос или проблема

Мой преподаватель дал задание написать программу, которая принимает ввод пользователя для имени, фамилии и процентной оценки студента и сохраняет данные и номер записи в текстовом файле, каждый элемент на отдельной строке и заданного количества символов в длину. Затем я должен использовать методы .read() и .seek() для извлечения данных и их вывода. Вот код, который я написал, чтобы попытаться выполнить задание:

LENGTHS = {"record_no":2, "firstname":10, "surname":10, "mark":3}
RECORD_LENGTH = 33


def get_record(record_no):
    with open("students.txt", "r") as students:
        record_start = record_no*RECORD_LENGTH
        students.seek(record_start)

        record = {"record_no":None, "firstname":None,
                  "surname":None, "mark":None}
        for key, length in LENGTHS.items():
            text = students.read(length)
            record[key] = text
 
            # Чтобы пропустить символ новой строки:
            current_pos = students.tell()
            students.seek(current_pos + 2)

    return(record)


with open("students.txt", "w") as students:
    students.write("{record_no:<{length}}\n".format(
        record_no=0, length=LENGTHS["record_no"]))

    firstname = input("Введите имя студента\n>>> ")
    students.write("{firstname:<{length}}\n".format(
        firstname=firstname, length=LENGTHS["firstname"]))

    surname = input("Введите фамилию студента\n>>> ")
    students.write("{surname:<{length}}\n".format(
        surname=surname, length=LENGTHS["surname"]))

    mark = input("Введите оценку студента\n>>> ")
    students.write("{mark:<{length}}\n".format(
        mark=mark, length=LENGTHS["mark"]))

print(get_record(0))

Однако, когда я использовал приведенный выше код, программа не выдала ожидаемый результат. Например, при вводе “Артур” в качестве имени, “Дент” в качестве фамилии и “42” в качестве оценки я получаю вывод:

{'record_no': '0 ', 'firstname': 'Arthur    ', 'surname': '\nent      ', 'mark': '\n2 '}

Изначально у меня была строка

students.seek(current_pos + 2)

как

students.seek(current_pos + 1)

но это оставляло символы новой строки в выводе:

{'record_no': '0 ', 'firstname': '\nArthur   ', 'surname': '\nDent     ', 'mark': '\n42'}

Я добавил несколько операторов print в код, чтобы вывести положение указателя файла после каждого вызова .read() или .seek(), и вот вывод:

Начальная позиция: 0

Длина record_no: 2
Позиция после record_no: 2
Позиция после пропуска символа новой строки: 4

Длина firstname: 10
Позиция после firstname: 18446744073709551631
Позиция после пропуска символа новой строки: 18446744073709551633

Длина surname: 10
Позиция после surname: 18446744073709551643
Позиция после пропуска символа новой строки: 18446744073709551645

Длина mark: 3
Позиция после mark: 18446744073709551648
Позиция после пропуска символа новой строки: 18446744073709551650

После чтения firstname указатель файла должен был находиться на позиции 14, но, очевидно, это не так. Похоже, что указатель файла может достигать таких позиций, если его заставить перейти на позицию, которая не существует (предполагаю, что это означает за пределами конца файла), но я убедился, что метод .read() не заставляет меня превышать длину файла (которая, когда хранится 1 запись, равна RECORD_LENGTH: 33). Я также использовал .seek(), чтобы напрямую перейти к символам (и их связанным значениям Unicode) в позициях, где возникла проблема, но ничего не показалось неправильным на выводах, за исключением того, что когда я распечатывал все символы в файле сразу, выводилось только 29 символов, а не 33. Когда я печатал некоторые символы в определенных позициях, казалось, что всегда рядом друг с другом были 2 символа новой строки (не так, как это выглядит в текстовом файле), но только 1 выводился, когда я распечатывал весь файл.

Вот формат текстового файла для справки:

0 
Arthur    
Dent      
42 

Я понимаю, что есть простые обходные решения, но меня больше интересует, что на самом деле происходит в коде. Буду признателен за любые ответы по оптимальным решениям и особенно объяснения проблемы.

Ответ или решение

Почему метод .read() приводит к перемещению указателя файла в неожиданные позиции

Ваша проблема вызывает интерес, так как она касается нескольких аспектов работы с файлами в Python. Разберём причины, по которым метод .read() может приводить к неожиданным позициям указателя файла.

1. Понимание .read() и .seek()

Метод .read(size) читает заданное количество байтов из текущей позиции в файле и перемещает указатель файла. Если не указать размер, то .read() считает этот размер равным количеству оставшихся байтов в файле. Таким образом, позиция указателя файла изменяется на количество символов, прочитанных методом.

Метод .seek(offset) перемещает указатель файла на заданное количество байтов от начала файла (или от конца файла, если указывается соответствующий параметр). Если после вызова .seek() указатель перемещается за конец файла, при последующих операциях чтения может возникнуть ошибка, или вы получите меньше данных, чем ожидали.

2. Разбор вашего кода

Ваша программа имеет конструкцию, которая в основном правильно организует процесс чтения и записи, но ключевая проблема возникает из-за неверного управления положением указателя файла после чтения.

Обратите внимание на эту часть вашего кода:

for key, length in LENGTHS.items():
    text = students.read(length)
    record[key] = text

    # Чтобы пропустить символ новой строки:
    current_pos = students.tell()
    students.seek(current_pos + 2)

В этом коде вы читаете нужный размер и потом перемещаете указатель на 2 байта вперед. Однако это приводит к ошибкам, если ваша запись о студенте заканчивается на символе новой строки (\n): вы фактически пропускаете больше байтов, чем нужно. Ваша запись имеет формат:

record_no\n
firstname\n
surname\n
mark\n

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

3. Подход к решению проблемы

Вместо того чтобы использовать .seek(current_pos + 2), вы можете просто следовать структуре записи, так как она уже содержит символы новой строки:

for key, length in LENGTHS.items():
    text = students.read(length + 1)  # Добавляем +1 для символа новой строки
    record[key] = text.strip()  # Убираем лишние пробелы и новые строки

    # Поскольку длина записи абсолюта всегда фиксированная, можно просто читать фиксированное количество символов.

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

4. Заключение

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

Если у вас останутся дополнительные вопросы по этой теме, не стесняйтесь обращаться!

Оцените материал
Добавить комментарий

Капча загружается...