4.3 Работа с файлами#

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

Открытие файла#

Для начала работы с любыми файлами нам потребуется знать две вещи:

  • путь к файлу

  • способ, которым мы его будем открывать

Про пути мы уже сказали, что они могут указываться как абсолютные, так и относительные. А также пути могут быть в виде строковых переменных, либо объектов Path

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

  • r - Открытие на чтение. Используется по умолчанию, можно не указывать.

  • w - Открытие на запись. Всегда создает новый файл. Если файл уже существовал, то полностью перезапишет его.

  • x - Открытие на запись с условием, что если файл уже существует, то произойдет ошибка открытия. Более безопасный способ создания новых файлов.

  • a - Открытие на дозапись.

И два дополнительных способа открытия, которые могут комбинироваться с предыдущими:

  • t - Открытие файла как текстового. Используется по умолчанию, можно не указывать.

  • b - Открытие файла как бинарного.

Определим сразу переменные path и acces_mode

path = r"data/file.txt"
access_mode = "r"

Для чтения файла используется встроенная функция open. Она принимает один обязательный параметр - путь.
Дополнительно могут быть заданы и другие параметры. Так, обычно вторым параметром является способ открытия - уровень доступа. Часто он указывается даже при открытии на чтение, чтобы явно указать цель открытия файла человеку, который будет работать с этим кодом в дальнейшем.

file = open(path, 'r')
file.close()  # любой открытый файл нужно в конце закрыть

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

file = open(path, access_mode, encoding="utf8")  # Обычно принято указывать путь, способ открытия и кодировку
text = file.read()  # метод чтения, читает весь текст в одну переменную
file.close()

print(text)
1 2 3 4
5 6 7 8
а б с д

Чтение файла#

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

file = open(path, access_mode, encoding="utf8")

# С помощью метода tell можно узнать, где каретка сейчас
print("Положение каретки в начале:", file.tell())

text = file.read()
print("Положение каретки после прочтения всего файла:", file.tell())

# С помощью метода seek каретку можно сдвинуть
file.seek(0)
print("Положение каретки после сдвига:", file.tell())

# В функции чтения можно передать число, которое ограничит количество читаемых символов
text = file.read(5)
print("Положение каретки в конце:", file.tell())
print("Прочитанный текст:", text)
file.close()
Положение каретки в начале: 0
Положение каретки после прочтения всего файла: 27
Положение каретки после сдвига: 0
Положение каретки в конце: 5
Прочитанный текст: 1 2 3

Другие методы для чтения

# readline - читает только одну строку
file = open(path, access_mode, encoding="utf8")
text_line_1 = file.readline(3)  # В функцию можно передать число, которое ограничит количество читаемых символов
text_line_2 = file.readline()  # Читает с текущего положения каретки до символа переноса строки (либо до указанного ограничения)
print(text_line_1)
print(text_line_2)
file.close()
1 2
 3 4
# readlines - читает файл в список из строк
file = open(path, access_mode, encoding="utf8")
text_lines = file.readlines(10)  # В функцию можно передать число, которое ограничит количество читаемых символов
                                 # Если ограничение попало в середину строки, то она все равно будет прочитана полностью
print(text_lines)
file.close()
['1 2 3 4\n', '5 6 7 8\n']

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

file = open(path, access_mode, encoding="utf8")
for line in file.readlines():
    print(line)
file.close()
1 2 3 4

5 6 7 8

а б с д
file = open(path, access_mode, encoding="utf8")
for line in file:  # при итерациях по файлу перебираются именно строки
    print(line)
file.close()
1 2 3 4

5 6 7 8

а б с д

Запись файла#

Если при чтении файла у нас была функция для чтения файла в одну текстовую переменную - read и функция для чтения построчно в список из строк readlines, то для записи по аналогии используются write - для записи одной текстовой переменной в файл и writelines - для записи последовательности текстовых переменных.

new_path = r"data/text.txt"
file = open(new_path, "w", encoding="utf8")  # Запись всегда создает новый файл
file.write("string1")  # Также используется каретка, запись происходит в месте, где она находится, и передвигает её
file.write(str(123))  # Можно записать только строки. Разделителей строк по умолчанию нет.
file.close()
file = open(new_path, "w", encoding="utf8")
file.writelines(["string1", str(123)])  # Как вариант можно записать список из строк, разделителей также не будет
file.close()
file = open(new_path, "w", encoding="utf8")
file.write("string1\n")  # Переносы строк нужно расставлять вручную
file.write("\n".join(["string2", "string3", "444"]))  # Либо можно воспользоваться методом строк join
file.close()

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

Контекстный менеджер#

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

Контекстный менеджер реализуется с помощью конструкции with.
Работа с файлами практически всегда осуществляется с её помощью.

with <Выражение> as <Переменная>:
  Блок инструкций

Условно можно сказать, что конструкция with работает как операция присваивания, а по завершении блока закрывает переменную (выполняет для неё метод __exit__, для файлов внутри метода __exit__ вызывается метод close)

with open(path, encoding="utf8") as file:
    lines = file.readlines()
    print(lines)
['1 2 3 4\n', '5 6 7 8\n', 'а б с д']

В конструкции with может быть открыто сразу несколько файлов. Главное - следить, чтобы сохранялась читаемость кода

with open(path, encoding="utf8") as file, open(new_path, "w", encoding="utf8") as new_file:
    lines = file.readlines()
    new_file.writelines(lines)