12  Работа с вводом и выводом

12.1 Input-Output

Файл - поименованная часть информации на носителе информации. Вся наша работа с компьютером связана с файлами, работа операционной системы основывается на файлах, поэтому язык программирования должен уметь работать с файлами. Работа с файлами является частью общей системы над обработкой пользовательского ввода-вывода (input-output, I/O). Пользователь вводит какую-то информацию и получает какой-то вывод, ответ на свою задачу. Введенная информация содержится в оперативной памяти в виде потоков (streams). Каждая программа (процесс) содержит три потока:

  1. Поток ввода
  2. Поток вывода
  3. Поток ошибок - похож на поток вывода. Сюда выводят информацию о нештатных ситуациях.

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

12.1.1 Операции над файлами

Чтобы начать работу с файлом, его необходимо открыть.

my_file = open("path/to/my/file","r", encoding = "utf-8")

Переменная my_file содержит специальный объект, содержащий в себе функции по работе с файлом. Функция open принимает множество параметров, но основных три:

  1. Путь к файлу в файловой системе. Файловая система представляет из себя дерево каталогов и файлов, поэтому путь к файлу записывается как перечисление каталогов, записанное с некоторым разделителем, по которым надо пройти от корня файловой системы до нужного файла.
  2. Режим открытия файлов: файлы бывают текстовые (человекочитаемые, из символов), бинарные (совокупность байтов). По умолчанию файл открывается как текстовый. Если вы хотите открыть его как бинарный, то необходимо к букве режима открытия приписать b.
    1. r - read. Файл открыт только для чтения. Вы не сможете изменить содержимое

    2. w - write. Файл откроется для записи. Если файл не существует, он будет создан

      ВНИМАНИЕ

      Если файл существует, то всё его содержимое будет утрачено. Используйте аккуратно!

    3. a - append. Файл доступен для чтения и записи в конец файла. Указатель для изменения можно перемещаь по файлу. Используйте этот режим для редактирования существующих файлов.

  3. Кодировка файла - принципы хранения символов (используемая кодовая таблица)
    1. UTF-8 - используется по умолчанию в UNIX-системах (Linux, MacOS)
    2. cp1251 - используется по умолчанию в Windows

Существуют несколько особенностей связанных с путями к файлам в зависимости от используемой операционной системы.

  1. Разделитель
    • В Windows разделителем является обратный слэш (\). Из-за подобного разделителя возникают некоторые проблемы: в сочетании с некоторыми символами обратный слэш интерпретируется как служебный символ (escape sequence), из-за чего строка с путём неправильно прочитывается интерпретатором. Примеры таких символов представлены в Table 12.1.

      Table 12.1: Примеры управляющих символов (escape sequences)
      Символ Значение
      \t табуляция
      \r возврат каретки
      \n перенос строки
      \u символ юникода
    • В Unix-системах (Linux, macOS) такой проблемы нет. Разделителем является прямой слэш (/).

  2. Местоположение корня
    1. В Windows корнем файловой системы является том, который обозначается одной заглавной латинской буквой (C или D).
    2. В Unix-системах корень обозначается просто слэшом.

Существуют два типа путей (независимо от ОС):

  • Абсолютные - полный путь от корня до целевого файла (“С:\Users\User\Document\my_file.txt”)

  • Относительные - путь до целевого файла от текущего местоположения. (Допустим, это ““С:\Users\User”, тогда относительный путь будет равен “Document\my_file.txt”)

Модуль os и его подмодули содержат большое количество функций для работы с файлами и путями

import os
1print(os.getcwd())
2os.chdir(path)
3os.exists(path)
4os.isfile(path)
5os.isdir(path)
6path = os.path.join(dir1,dir2,dir3)
1
Получить текущую директорию в виде строки
2
Изменить текущую директорию
3
Проверка на существование файла/папки
4
Проверка на то, является ли объект указанного пути существующим файлом
5
Проверка на то, является ли объект указанного пути существующей папкой
6
Рекомендуемый способ составления путей из нескольких компонентов независимо от операционной системы

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

Listing 12.1: Чтение файлов в Python
file = open("some/path/to.txt")
content = file.read()
first_5_bytes = file.read(5)
lines = file.readlines()
  1. Открытие файла. По умолчанию файл открывается в кодировке utf-8 для чтения
  2. Прочитать всё содержимое файла
  3. Прочитать первые 5 символов
  4. Прочитать файл построчно - вернет список, в котором элементами будут являтся строки. Строкой в данном случае является совокупность символов до знака переноса строки (\n).

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

file = open("some/path/to.txt")

1for line in file:
    print(line)

2chunk = file.read(30)
while chunk:
    print(chunk)
    chunk = file.read(30)
1
Чтение файла в цикле построчно
2
Чтение кусками (чанками) по 30 символов

Запись в файл и работа с курсором рассмотрена на Listing 12.2

Listing 12.2: Запись в файл в Python
file = open("some/path/to.txt","w")
file.write("Meow meow")
file.write(line + "\n")
file.tell()
file.seek(offset, whenence)
  1. Открытие файла на запись
  2. Запись некоторой последовательности символов в файл
  3. Если вы хотите записать последовательность символов как строку файла, тогда надо прибавить символ переноса строки (если последовательность символов изначально на него не заканчивается).
  4. Получить текущее положение указателя
  5. Сместить указатель на offset байт относительно whenence. whenence может принимать три значения: 0 - начало файла, 1 - текущее местоположение, 2 - конец файла.

После окончания работы файл необходимо обязательно закрыть!

file.close()

Какие могут быть последствия незакрытия файла.

  1. Ваши изменения не сохранятся, так как не перейдут из буфера в файл.
  2. Файл может остаться заблокированным для открытия другими программами и частями вашей программы.
  3. Возникнет неприятное являние, которое будет тормозить работу выполнения вашей и других программ - утечка памяти. Утечка памяти - состояние, когда не освобождается выделенная память после окончания её использования.

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

with open("my_file.txt") as file:
    for line in file:
        print(line)

Менеджер контекста сам закроет файл по окончанию работы с ним, даже в случае внештатной ситуации. Не нужно самим вызывать функцию close.

12.2 Упражнения

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