Чтение и запись в файл

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

open(file [, mode=’r’, encoding=None, …])

через которую и осуществляется работа с файлами. Здесь

  • file – это путь к файлу вместе с его именем;
  • mode – режим доступа к файлу;
  • encoding – кодировка файла.

Для начала определимся с понятием «путь к файлу». Представим, что наш файл ex1.py находится в каталоге app:

Тогда, чтобы обратиться к файлу my_file.txt путь можно записать так:

"my_file.txt"

или

"d:\\app\\my_file.txt"

или так:

"d:/app/my_file.txt"

Последние два варианта представляют собой абсолютный путь к файлу, то есть, полный путь, начиная с указания диска. Причем, обычно используют обратный слеш в качестве разделителя: так короче писать и такой путь будет корректно восприниматься как под ОС Windows, так и Linux. Первый же вариант – это относительный путь, относительно рабочего каталога.

Теперь, предположим, мы хотим обратиться к файлу img.txt. Это можно сделать так:

"images/img.txt"

или так:

"d:/app/images/img.txt"

Для доступа к out.txt пути будут записаны так:

"../out.txt"

"d:/out.txt"

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

И, наконец, для доступа к файлу prt.dat пути запишутся так:

"../parent/prt.dat"

"d:/ parent/prt.dat"

Вот так следует прописывать пути к файлам. В нашем случае мы имеем текстовый файл «myfile.txt», который находится в том же каталоге, что и программа ex1.py, поэтому путь можно записать просто указав имя файла:

file = open("myfile.txt")

В результате переменная file будет ссылаться на файловый объект, через который и происходит работа с файлами. Если указать неверный путь, например, так:

file = open("myfile2.txt")

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

try:
    file = open("myfile2.txt")
except FileNotFoundError:
    print("Невозможно открыть файл")

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

mode = "r"

Если нам нужно поменять режим доступа к файлу, например, открыть его на запись, то это явно указывается вторым параметром функции open:

file = open("out.txt", "w")

В Python имеются следующие режимы доступа:

Название

Описание

'r'

открытие на чтение (значение по умолчанию)

'w'

открытие на запись (содержимое файла удаляется, а если его нет, то создается новый)

'x'

открытие файла на запись, если его нет генерирует исключение

'a'

открытие на дозапись (информация добавляется в конец файла)

Дополнения

'b'

открытие в бинарном режиме доступа к информации файла

't'

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

'+'

открытие на чтение и запись одновременно

Здесь мы имеем три основных режима доступа: на чтение, запись и добавление. И еще три возможных расширения этих режимов, например,

  • 'rt' – чтение в текстовом режиме;
  • 'wb' – запись в бинарном режиме;
  • 'a+' – дозапись или чтение данных из файла.

Чтение информации из файла

В чем отличие текстового режима от бинарного мы поговорим позже, а сейчас откроем файл на чтение в текстовом режиме:

file = open("myfile.txt")

и прочитаем его содержимое с помощью метода read:

print( file.read() )

В результате, получим строку, в которой будет находиться прочитанное содержимое. Действительно, в этом файле находятся эти строчки из поэмы Пушкина А.С. «Медный всадник». И здесь есть один тонкий момент. Наш текстовый файл имеет кодировку Windows-1251 и эта кодировка используется по умолчанию в функции read. Но, если изменить кодировку файла, например, на популярную UTF-8, то после запуска программы увидим в консоли вот такую белиберду. Как это можно исправить, не меняя кодировки самого файла? Для этого следует воспользоваться именованным параметром encoding и записать метод open вот так:

file = open("myfile.txt", encoding="utf-8" )

Теперь все будет работать корректно. Далее, в методе read мы можем указать некий числовой аргумент, например,

print( file.read(2) )

Тогда из файла будут считаны первые два символа. И смотрите, если мы запишем два таких вызова подряд:

print( file.read(2) )
print( file.read(2) )

то увидим, что при следующем вызове метод read продолжил читать следующие два символа. Почему так произошло? Дело в том, что у файлового объекта, на который ссылается переменная file, имеется внутренний указатель позиции (file position), который показывает с какого места производить считывание информации.

Когда мы вызываем метод read(2) эта позиция автоматически сдвигается от начала файла на два символа, т.к. мы именно столько считываем. И при повторном вызове read(2) считывание продолжается, т.е. берутся следующие два символа. Соответственно, позиция файла сдвигается дальше. И так, пока не дойдем до конца.

Но мы в Python можем управлять этой файловой позицией с помощью метода

seek(offset[, from_what])

Например, вот такая запись:

file.seek(0)

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

print( file.read(2) )
file.seek(0)
print( file.read(2) )

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

pos = file.tell()
print( pos )

Следующий полезный метод – это readline позволяет построчно считывать информацию из текстового файла:

s = file.readline()
print( s )

Здесь концом строки считается символ переноса ‘\n’, либо конец файла. Причем, этот символ переноса строки будет также присутствовать в строке. Мы в этом можем убедиться, вызвав дважды эту функцию:

    print( file.readline() )
    print( file.readline() )

Здесь в консоли строчки будут разделены пустой строкой. Это как раз из-за того, что один перенос идет из прочитанной строки, а второй добавляется самой функцией print. Поэтому, если их записать вот так:

    print( file.readline(), end="" )
    print( file.readline(), end="" )

то вывод будет построчным с одним переносом.

Если нам нужно последовательно прочитать все строчки из файла, то для этого обычно используют цикл for следующим образом:

    for line in file:
        print( line, end="" )

Этот пример показывает, что объект файл является итерируемым и на каждой итерации возвращает очередную строку.

Или же, все строчки можно прочитать методом

s = file.readlines()

и тогда переменная s будет ссылаться на упорядоченный список с этими строками:

print( s )

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

По сути это все методы для считывания информации из файла. И, смотрите, как только мы завершили работу с файлом, его следует закрыть. Для этого используется метод close:

file.close()

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

try:
    file = open("myfile.txt")
 
    try:
        s = file.readlines()
        print( s )
    finally:
        file.close()
 
except FileNotFoundError:
    print("Невозможно открыть файл")

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

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

try:
    with open("myfile.txt", "r") as file:      # file = open("myfile.txt")
        s = file.readlines()
        print( s )
 
except FileNotFoundError:
    print("Невозможно открыть файл")

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

finally:
    print(file.closed)

Запустим программу, видите, все работает также и при этом файл автоматически закрывается. Даже если произойдет критическая ошибка, например, пропишем такую конструкцию:

print( int(s) )

то, как видим, файл все равно закрывается. Вот в этом удобство такого подхода при работе с файлами.

Запись информации в файл

Теперь давайте посмотрим, как происходит запись информации в файл. Во-первых, нам нужно открыть файл на запись, например, так:

file = open("out.txt", "w")

и далее вызвать метод write:

file.write("Hello World!")

В результате у нас будет создан файл out.txt со строкой «Hello World!». Причем, этот файл будет располагаться в том же каталоге, что и файл с текстом программы на Python.

Далее сделаем такую операцию: запишем метод write следующим образом:

file.write("Hello")

И снова выполним эту программу. Смотрите, в нашем файле out.txt прежнее содержимое исчезло и появилось новое – строка «Hello». То есть, когда мы открываем файл на запись в режимах

w, wt, wb,

то прежнее содержимое файла удаляется. Вот этот момент следует всегда помнить.

Теперь посмотрим, что будет, если вызвать метод write несколько раз подряд:

    file.write("Hello1")
    file.write("Hello2")
    file.write("Hello3")

Смотрите, у нас в файле появились эти строчки друг за другом. То есть, здесь как и со считыванием: объект file записывает информацию, начиная с текущей файловой позиции, и автоматически перемещает ее при выполнении метода write.

Если мы хотим записать эти строчки в файл каждую с новой строки, то в конце каждой пропишем символ переноса строки:

   file.write("Hello1\n")
   file.write("Hello2\n")
   file.write("Hello3\n")

Далее, для дозаписи информации в файл, то есть, записи с сохранением предыдущего содержимого, файл следует открыть в режиме ‘a’:

file = open("out.txt", "a")

Тогда, выполняя эту программу, мы в файле увидим уже шесть строчек. И смотрите, в зависимости от режима доступа к файлу, мы должны использовать или методы для записи, или методы для чтения. Например, если вот здесь попытаться прочитать информацию с помощью метода read:

file.read()

то возникнет ошибка доступа. Если же мы хотим и записывать и считывать информацию, то можно воспользоваться режимом a+:

file = open("out.txt", "a+")

Так как здесь файловый указатель стоит на последней позиции, то для считывания информации, поставим его в самое начало:

   file.seek(0)
   print( file.read() )

А вот запись данных всегда осуществляется в конец файла.

Следующий полезный метод для записи информации – это writelines:

file.writelines(["Hello1\n", "Hello2\n"])

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

Чтение и запись в бинарном режиме доступа

Что такое бинарный режим доступа? Это когда данные из файла считываются один в один без какой-либо обработки. Обычно это используется для сохранения и считывания объектов. Давайте предположим, что нужно сохранить в файл вот такой список:

books = [
("Евгений Онегин", "Пушкин А.С.", 200),
("Муму", "Тургенев И.С.", 250),
("Мастер и Маргарита", "Булгаков М.А.", 500),
("Мертвые души", "Гоголь Н.В.", 190)
]

Откроем файл на запись в бинарном режиме:

file = open("out.bin", "wb")

Далее, для работы с бинарными данными подключим специальный встроенный модуль pickle:

import pickle

И вызовем него метод dump:

pickle.dump(books, file)

Все, мы сохранили этот объект в файл. Теперь прочитаем эти данные. Откроем файл на чтение в бинарном режиме:

file = open("out.bin", "rb")

и далее вызовем метод load модуля pickle:

bs = pickle.load(file)

Все, теперь переменная bs ссылается на эквивалентный список:

print( bs )

Аналогичным образом можно записывать и считывать сразу несколько объектов. Например, так:

import pickle
 
book1 = ["Евгений Онегин", "Пушкин А.С.", 200]
book2 = ["Муму", "Тургенев И.С.", 250]
book3 = ["Мастер и Маргарита", "Булгаков М.А.", 500]
book4 = ["Мертвые души", "Гоголь Н.В.", 190]
 
try:
    file = open("out.bin", "wb")
 
    try:
        pickle.dump(book1, file)
        pickle.dump(book2, file)
        pickle.dump(book3, file)
        pickle.dump(book4, file)
 
    finally:
        file.close()
 
except FileNotFoundError:
    print("Невозможно открыть файл")

А, затем, считывание в том же порядке:

    file = open("out.bin", "rb")
    b1 = pickle.load(file)
    b2 = pickle.load(file)
    b3 = pickle.load(file)
    b4 = pickle.load(file)
 
    print( b1, b2, b3, b4, sep="\n" )

Вот так в Python выполняется запись и считывание данных из файла.

Задания для самоподготовки

1. Выполните считывание данных из текстового файла через символ и записи прочитанных данных в другой текстовый файл. Прочитывайте так не более 100 символов.

2. Пользователь вводит предложение с клавиатуры. Разбейте это предложение по словам (считать, что слова разделены пробелом) и сохраните их в столбец в файл.

3. Пусть имеется словарь:

d = {"house": "дом", "car": "машина",
     "tree": "дерево", "road": "дорога",
     "river": "река"}

Необходимо каждый элемент этого словаря сохранить в бинарном файле как объект. Затем, прочитать этот файл и вывести считанные объекты в консоль.

Видео по теме