На этом занятии
мы поговорим, как в 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:
В результате, получим
строку, в которой будет находиться прочитанное содержимое. Действительно, в
этом файле находятся эти строчки из поэмы Пушкина А.С. «Медный всадник». И
здесь есть один тонкий момент. Наш текстовый файл имеет кодировку Windows-1251 и эта
кодировка используется по умолчанию в функции read. Но, если
изменить кодировку файла, например, на популярную UTF-8, то после
запуска программы увидим в консоли вот такую белиберду. Как это можно
исправить, не меняя кодировки самого файла? Для этого следует воспользоваться
именованным параметром encoding и записать метод open вот так:
file = open("myfile.txt", encoding="utf-8" )
Теперь все будет
работать корректно. Далее, в методе read мы можем
указать некий числовой аргумент, например,
Тогда из файла будут
считаны первые два символа. И смотрите, если мы запишем два таких вызова
подряд:
print( file.read(2) )
print( file.read(2) )
то увидим, что
при следующем вызове метод read продолжил читать следующие два символа.
Почему так произошло? Дело в том, что у файлового объекта, на который ссылается
переменная file, имеется
внутренний указатель позиции (file position), который
показывает с какого места производить считывание информации.
Когда мы
вызываем метод read(2) эта позиция автоматически сдвигается от начала
файла на два символа, т.к. мы именно столько считываем. И при повторном вызове read(2) считывание
продолжается, т.е. берутся следующие два символа. Соответственно, позиция файла
сдвигается дальше. И так, пока не дойдем до конца.
Но мы в Python можем управлять
этой файловой позицией с помощью метода
seek(offset[, from_what])
Например, вот такая запись:
будет означать,
что мы устанавливаем позицию в начало и тогда такие строчки:
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 будет ссылаться
на упорядоченный список с этими строками:
Однако этот
метод следует использовать с осторожностью, т.к. для больших файлов может
возникнуть ошибка нехватки памяти для хранения полученного списка.
По сути это все
методы для считывания информации из файла. И, смотрите, как только мы завершили
работу с файлом, его следует закрыть. Для этого используется метод 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)
Запустим
программу, видите, все работает также и при этом файл автоматически
закрывается. Даже если произойдет критическая ошибка, например, пропишем такую
конструкцию:
то, как видим,
файл все равно закрывается. Вот в этом удобство такого подхода при работе с
файлами.
Запись информации в файл
Теперь давайте
посмотрим, как происходит запись информации в файл. Во-первых, нам нужно
открыть файл на запись, например, так:
file = open("out.txt", "w")
и далее вызвать
метод write:
file.write("Hello World!")
В результате у
нас будет создан файл out.txt со строкой «Hello World!». Причем, этот
файл будет располагаться в том же каталоге, что и файл с текстом программы на Python.
Далее сделаем
такую операцию: запишем метод write следующим
образом:
И снова выполним
эту программу. Смотрите, в нашем файле 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:
то возникнет
ошибка доступа. Если же мы хотим и записывать и считывать информацию, то можно воспользоваться
режимом 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:
И вызовем него
метод dump:
Все, мы
сохранили этот объект в файл. Теперь прочитаем эти данные. Откроем файл на
чтение в бинарном режиме:
file = open("out.bin", "rb")
и далее вызовем
метод load модуля pickle:
Все, теперь
переменная 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":
"река"}
Необходимо
каждый элемент этого словаря сохранить в бинарном файле как объект. Затем,
прочитать этот файл и вывести считанные объекты в консоль.