В 'r +', почему запись текстового файла * после * чтения одной строки заставляет его писать в конце вместо позиции `f.tell ()`? - PullRequest
3 голосов
/ 06 ноября 2019

Имея такой текстовый файл:

line one
line two
line three

И запуская следующий код:

with open('file', 'r+') as f:
    print(f.tell())
    print(f.readline().strip())
    print(f.tell())
    # f.seek(f.tell())
    f.write('Hello')
    print(f.tell())

В результате слово "Hello" будет написано в самом конце файла:

line one
line two
line threeHello

Я думал, что часть записи будет начинаться с позиции последнего прочитанного символа (сразу после line one), но это не так, если я не раскомментирую f.seek(f.tell()). Могут быть некоторые основные принципы, которые я пропускаю, но я не могу найти ничего в документации Python, которая подробно объясняет, как это работает. Что здесь происходит, что заставляет его писать там слово? И почему этого не происходит, если я сначала не читаю, а вместо этого начинаю писать?

Печатные значения для f.tell() следующие:

0
9
39

Ответы [ 2 ]

3 голосов
/ 06 ноября 2019

Это похоже на ошибку в том, как io.TextIOWrapper (класс, возвращаемый open в текстовом режиме) взаимодействует с io.BufferedRandom (классом, который он переносит в режимах +).

Если вы измените свой тестовый пример на работу в двоичном режиме:

with open('file', 'rb+') as f:
    print(f.tell())
    print(f.readline().strip())
    print(f.tell())
    # f.seek(f.tell())
    f.write(b'Hello')
    print(f.tell())

поведение будет одинаковым независимо от того, включено или нет лишнее f.seek(f.tell()).

Возможно, проблема вызвананесколькими слоями буферизации. Вы получаете io.TextIOWrapper, обертывающий io.BufferedRandom (который, в свою очередь, оборачивает io.FileIO). TextIOWrapper читает фрагменты из io.BufferedRandom, чтобы амортизировать стоимость декодирования из байтов в текст, поэтому, когда вы вызываете readline, он фактически потребляет и декодирует весь ваш файл (он настолько мал, что помещается в один блок), оставляяBufferedRandom расположен в конце файла (хотя логически он должен быть только на полпути, а TextIOWrapper.tell сообщает о позиции, соответствующей этой логической позиции).

Когда вы поворачиваетесь и write,TextIOWrapper кодирует данные и передает их в BufferedRandom, который по-прежнему считает себя в конце файла;поскольку TextIOWrapper не исправляет это, данные прикрепляются до конца. Кажущийся no-op f.seek(f.tell()) ресинхронизирует TextIOWrapper с подчиненным BufferedRandom, чтобы получить ожидаемое поведение. В этом нет необходимости (я рекомендую подавать ошибку , чтобы write перешел в логическую позицию tell, так как я не могу найти существующую ошибку, хотя Python 3Функция f.tell () не синхронизируется с указателем на файл в двоичном приложении (режим чтения + внешне похож), но, по крайней мере, обходной путь относительно прост.

1 голос
/ 06 ноября 2019

Проблема связана с буферизованным вводом-выводом.

Функция open (), кажется, открывает дескриптор буферизованного файла.

Таким образом, фактически, когда что-то из файла читается, по крайней мере,Читается весь буфер, который, кажется, на моей машине около 8 КБ (8192) байтов. Это для оптимизации производительности.

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

f.tell () дает вам относительную позициюв байты, которые уже были возвращены readline ().

. Вы можете принудительно указать указатель записи с помощью f.seek (f.tell ()) в место, которое вы намеревались. Без явного оператора поиска вы будете писать после буфера.

Используйте следующий скрипт, чтобы проиллюстрировать и посмотреть на вывод:

Вы увидите, что я пытался играть с параметром buffering,Соответствие doc 1 означает буферизацию строки, но я не вижу никаких изменений в поведении.

with open("file", "w") as f:
    f.write(("*" * 79 +"\n") * 1000)

with open('file', 'r+', buffering=1) as f:
    print(f.tell())
    print(f.readline().strip())
    print(f.tell())
    # f.seek(f.tell())
    f.write('Hello')
    print(f.tell())

print("----------- file contents")
with open("file", "r") as f:
    pass
    print(f.read())
print("----------- END")

Так что если вы пишете после readline (), то он будет записывать новые данные после буфера,это читается в.

f.tell () с другой стороны возвращает вам позицию, которая сообщает вам, сколько байтов уже возвращено.

Вывод будет:

0
*******************************************************************************
80
8197
8202
----------- file contents
*******************************************************************************
*******************************************************************************
...
*******************************************************************************
********************************HelloHello*************************************
*******************************************************************************
*******************************************************************************
*******************************************************************************
*******************************************************************************
...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...