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

По способу хранения информации все файлы делятся на текстовые и бинарные (двоичные).

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

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

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

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

Открытие файлов

Прежде чем начать читать или записывать данные, файл необходимо открыть. Функция open() выполняет эту задачу и возвращает файловый объект. Он используется для всех последующих операций чтения и записи, а также для закрытия файла после завершения работы с ним.

Функция

open(file, mode="r", encoding=None)

Описание

Открывает файл file для чтения или записи

Параметры

  • file – абсолютный или относительный путь к файлу

Необязательные параметры:

  • mode – режим открытия файла, по умолчанию mode="r"
  • encoding – кодировка для перевода байтов в символы (для текстовых файлов). По умолчанию не задана

Возвращаемое значение

Файловый объект

По умолчанию файл открывается в режиме чтения ("r"), то есть данные из файла можно прочитать, но нельзя добавить новые. Однако существуют режимы записи и дозаписи, которые можно совмещать с чтением с помощью символа +.

Режимы открытия текстового файла

Режим

Сокращение от

Действия

Начальная позиция указателя

Файл не существует

Файл очищается при открытии

"r"

read (с англ. – читать)

Чтение

Начало файла

Вызывается исключение FileNotFoundError

Нет

"r+"

Чтение и перезапись

"w"

write (с англ. – записать)

 

Запись

Начало файла

Файл создаётся

Да

"w+"

Запись и чтение

"a"

append (с англ. – добавить)

Дозапись

Конец файла

Файл создаётся

Нет

"a+"

Дозапись и чтение

Режимы открытия файлов в Python делятся на текстовые и двоичные. При чтении текстовых файлов все методы возвращают строки, а двоичных – байты. Для работы с бинарными файлами (изображения, исполняемые файлы) к текстовому режиму добавляется буква "b" (от англ. binary – бинарный), например, "rb" или "wb".

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

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

Давайте создадим текстовый файл data.txt с произвольным текстом и скрипт main.py, в котором откроем созданный файл:

data_file = open("data.txt", encoding="utf-8")

Путь к файлу может быть абсолютным и начинаться с буквы диска, например, "С:\projects\tasks\data.txt", или относительным и указываться относительно запущенного файла. Поэтому если файл находится в той же папке, что и программа, достаточно указать только его имя.

Кроме того, так как в большинстве случаев мы работаем с кириллицей, рекомендуется указывать кодировку "utf-8".

Если запустить написанный код, то ничего не произойдёт, так как файл существует, и программа завершится корректно. Но если удалить файл data.txt и снова запустить программу, то будет вызвано исключение FileNotFoundError.

В зависимости от режима открытия файла изменяется требование к его существованию. Так, режимы записи ("w"), дозаписи ("a") и комбинированные режимы ("r+", "w+", "a+") создадут новый файл при его отсутствии:

data_file = open("new_data.txt", mode="w", encoding="utf-8")

После выполнения этого кода в папке со скриптом у вас должен создаться новый текстовый файл new_data.txt.

Закрытие файла и контекстный менеджер

В конце работы с файлом его необходимо закрыть с помощью метода file.close(). Это нужно для освобождения системных ресурсов, обеспечения целостности файла и избегания потери данных. Также только после закрытия файла гарантируется, что все данные из оперативной памяти были успешно записаны в файл.

Метод

file.close()

Описание

Закрывает открытый файл file

Возвращаемое значение

None

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

with open(путь_к_файлу, режим, кодировка) as имя_объекта_с_файлом:
    # Блок кода для работы с файлом
# Здесь файл автоматически закрывается

Он гарантирует, что метод file.close() будет вызван автоматически, независимо от того, как завершился блок кода with – успешно или с исключением. Это стандартный и рекомендуемый способ работы с файлами в Python.

Чтение данных из файла

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

  • file.read() – считывает все содержимое файла в одну строку;
  • file.readline() – считывает одну строку из файла;
  • file.readlines() – считывает все строки файла в список.

Выбор метода зависит от размера файла и от того, как требуется обрабатывать его содержимое.

При этом строки как тип str считываются только в текстовых файлах. Если открыт бинарный файл, то все манипуляции производятся над неизменяемыми последовательностями байтов, которые в Python представлены типом bytes.

Для начала давайте подготовим данные для чтения и создадим файл cake_recipe.txt с классическим рецептом бисквита:

Яйцо куриное - 4 шт.
Мука пшеничная - 120 г
Сахар - 110 г
Ванильный сахар - 10 г

Чтение всего файла в строку

Метод file.read() является самым прямым способом, так как он читает всё содержимое файла целиком и возвращает его в виде одной строки.

Функция

file.read(size=-1)

Описание

Читает файл file и возвращает его содержимое в виде одной строки

Параметры

Необязательные параметры:

  • size – количество байтов, которое нужно прочитать. По умолчанию считывается весь файл

Возвращаемое значение

Строка

Для текстовых файлов метод file.read() возвращает одну большую строку, содержащую все символы файла, включая символы перевода строки (\n):

with open("cake_recipe.txt", "r", encoding="utf-8") as cake_recipe_file:
    print(cake_recipe_file.read())
    # Вывод: Яйцо куриное - 4 шт.
    #        Мука пшеничная - 120 г
    #        Сахар - 110 г
    #        Ванильный сахар - 10 г
При открытии файла в режиме записи ("w") или записи и чтения ("w+") содержимое файла удаляется без возможности восстановления. Поэтому будьте внимательны при выборе режима открытия файла.
Робот Кеша предупреждает

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

Однако параметр size позволяет указать максимальное количество символов для чтения за один раз:

with open("cake_recipe.txt", "r", encoding="utf-8") as cake_recipe_file:
    print(cake_recipe_file.read(4))
    # Вывод: Яйцо

Здесь из целого файла были получены только первые 4 символа.

Построчное чтение

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

Функция

file.readline(size=-1)

Описание

Читает файл file и возвращает одну строку (до символа \n включительно)

Параметры

Необязательные параметры:

  • size – количество байтов, которое нужно прочитать. По умолчанию считывается строка до символа \n

Возвращаемое значение

Строка

Этот метод читает символы от текущей позиции до ближайшего символа перевода строки (\n), включая сам этот символ, и возвращает их в виде строки:

with open("cake_recipe.txt", "r", encoding="utf-8") as cake_recipe_file:
    print(cake_recipe_file.readline(), end="")
    # Вывод: Яйцо куриное - 4 шт.

Здесь мы присваиваем параметру end функции print() значение в виде пустой строки для того, чтобы избежать дополнительной пустой строки при выводе, так как прочитанная строка уже заканчивается символом \n.

При каждом последующем вызове метода указатель перемещается на начало следующей строки:

with open("cake_recipe.txt", "r", encoding="utf-8") as cake_recipe_file:
    print(cake_recipe_file.readline(), end="")
    # Вывод: Яйцо куриное - 4 шт.
    print(cake_recipe_file.readline(), end="")
    # Вывод: Мука пшеничная - 120 г
    print(cake_recipe_file.readline(), end="")
    # Вывод: Сахар - 110 г

Как и в методе file.read(), размер читаемой строки можно ограничить, просто передав методу целое число, указывающее количество байт, которые нужно прочитать:

with open("cake_recipe.txt", "r", encoding="utf-8") as cake_recipe_file:
    print(cake_recipe_file.readline(4), end="")
    # Вывод: Яйцо

Наиболее распространённым способом построчного чтения файла является перебор его в цикле for:

with open("cake_recipe.txt", "r", encoding="utf-8") as cake_recipe_file:
    for line in cake_recipe_file:
        print(line, end="")
    # Вывод: Яйцо куриное - 4 шт.
    # Вывод: Мука пшеничная - 120 г
    # Вывод: Сахар - 110 г
    # Вывод: Ванильный сахар - 10 г

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

Чтение всего файла в список строк

Метод file.readlines() сочетает удобство чтения всего файла с ориентацией на строки. Он читает все строки из файла до конца и возвращает их в виде списка строк.

Функция

file.readlines()

Описание

Читает файл file и возвращает список строк

Возвращаемое значение

Список строк

Каждый элемент списка будет содержать полный текст строки, включая символ \n:

with open("cake_recipe.txt", "r", encoding="utf-8") as cake_recipe_file:
    print(cake_recipe_file.readlines())
    # Вывод: ['Яйцо куриное - 4 шт.\n', 'Мука пшеничная - 120 г\n', 'Сахар - 110 г\n', 'Ванильный сахар - 10 г']

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

Запись данных в файл

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

  • file.write() – добавляет одну строку в файл;
  • file.writelines() – добавляет в файл все строки из списка.

Запись одной строки

Метод file.write() является базовым инструментом для записи данных в файл. Он принимает в качестве аргумента одну строку и записывает ее в файл, начиная с текущей позиции указателя.

Функция

file.write(line)

Описание

Записывает строку line в файл file

Параметры

  • line – строка, записываемая в файл file

Возвращаемое значение

Количество записанных символов

В зависимости от режима открытия файла меняется позиция указателя как начала операций чтения и записи:

  • начало файла (позиция 0) – "r+", "w" и "w+"
  • конец файла – "a" и "a+"

Запись одной строки в режиме r+ и управление указателем

Режим "r+" позволяет выполнять как чтение, так и запись в одном сеансе работы с файлом, при этом файл должен быть уже создан, иначе вызывается исключение FileNotFoundError.

Например, давайте откроем предыдущий файл с рецептом бисквита и запишем в него строку "Вода - 250 мл":

with open("cake_recipe.txt", "r+", encoding="utf-8") as cake_recipe_file:
    cake_recipe_file.write("Вода - 250 мл")
    print(cake_recipe_file.readlines())
    # Вывод: ['ое - 4 шт.\n', 'Мука пшеничная - 120 г\n', 'Сахар - 110 г\n', 'Ванильный сахар - 10 г']

Здесь новая строка перезаписывает содержимое файла, поэтому всё ещё остаётся часть старой строки "Яйцо куриное - 4 шт.\n", а любые операции чтения или записи проводятся с новой позиции указателя. Это значение позволяет получить метод file.tell().

Функция

file.tell()

Описание

Возвращает текущую позицию указателя в файле file

Возвращаемое значение

Текущая позиция указателя в байтах

Результат этого метода не всегда совпадает с количеством символов до указателя, так как в стандартной кодировке UTF-8 большинство кириллических символов занимают 2 байта:

with open("cake_recipe.txt", "r+", encoding="utf-8") as cake_recipe_file:
    cake_recipe_file.write("Вода - 250 мл")
    print(cake_recipe_file.tell())
    # Вывод: 19

При открытии файла указатель устанавливается в его начало (0 байт), а после записи строки "Вода - 250 мл" он перемещается на 19 байт вперёд. Однако мы можем вручную переместить указатель с помощью метода file.seek().

Функция

file.seek(offset, whence=0)

Описание

Перемещает указатель с текущей позиции на позицию offset относительно начала файла file

Параметры

  • offset – новая позиция указателя в файле file

Возвращаемое значение

Новая позиция указателя

В текстовых файлах указатель смещается на offset байтов относительно начала файла, поэтому для того, чтобы перевести его в начало файла, достаточно передать методу file.seek() число 0:

with open("cake_recipe.txt", "r+", encoding="utf-8") as cake_recipe_file:
    cake_recipe_file.write("Вода - 250 мл")

    cake_recipe_file.seek(0)

    print(cake_recipe_file.readlines())
    # Вывод: ['Вода - 250 млое - 4 шт.\n', 'Мука пшеничная - 120 г\n', 'Сахар - 110 г\n', 'Ванильный сахар - 10 г']

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

Запись одной строки в режимах w и w+

Режимы "w" и "w+" используются, когда нужно создать новый файл или полностью перезаписать содержимое существующего.

Если файл, открытый в одном из этих режимов, не существует, то он будет создан. Так, если мы заранее создадим файл cream_recipe.txt для рецепта крема с произвольным текстом, то его открытие в режимах "w" и "w+" полностью удалит его содержимое:

with open("cream_recipe.txt", "w+", encoding="utf-8") as cream_recipe_file:
    cream_recipe_file.write("Сахар - 240 г\n")
    cream_recipe_file.write("Масло сливочное - 260 г\n")

    cream_recipe_file.seek(0)  # Возвращаем указатель в начало файла

    print(cream_recipe_file.readlines())
    # Вывод: ['Сахар - 240 г\n', 'Масло сливочное - 260 г\n']

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

Так как метод file.write() не добавляет автоматически символ перевод строки (\n), то при необходимости он указывается вручную.

Запись одной строки в режимахa и a+

Режимы "a" и "a+" используются, когда нужно добавить новые данные в конец файла, сохраняя при этом старые данные. Если файл, открытый в одном из этих режимов, не существует, то он будет создан, однако в отличие от режимов "w" и "w+", его содержимое не будет перезаписано, а указатель устанавливается в конец файла.

Например, откроем файл cream_recipe.txt, в который мы уже записывали строки с ингредиентами, и запишем в него новые строки:

with open("cream_recipe.txt", "a+", encoding="utf-8") as cream_recipe_file:
    cream_recipe_file.write("Сметана 30% - 500 г\n")
    cream_recipe_file.write("Вареная сгущёнка - 380 г\n")

    cream_recipe_file.seek(0)  # Возвращаем указатель в начало файла

    print(cream_recipe_file.readlines())
    # Вывод: ['Сахар - 240 г\n', 'Масло сливочное - 260 г\n', 'Сметана 30% - 500 г\n', 'Вареная сгущёнка - 380 г\n']

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

Запись всех строк из списка

Если нужно добавить сразу несколько строк, то необязательно записывать их по одной, так как метод file.writelines() позволяет сразу записать последовательность (список или кортеж) строк.

Функция

file.writelines(sequence)

Описание

Записывает все строки из последовательности sequence в файл file

Параметры

  • sequence – последовательность строк для записи

Возвращаемое значение

None

Этот метод может использоваться в любом режиме, разрешающим запись. Например, программно создадим новый файл shopping.txt и добавим в него список покупок:

with open("shopping.txt", "w+", encoding="utf-8") as shopping_file:
    cake_ingredients = ["Мука\n", "Сахар\n", "Масло\n", "Сметана\n", "Сгущёнка\n"]
    shopping_file.writelines(cake_ingredients)

    shopping_file.seek(0)  # Возвращаем указатель в начало файла для чте-ния

    print(shopping_file.readlines())
    # Вывод: ['Мука\n', 'Сахар\n', 'Масло\n', 'Сметана\n', 'Сгущёнка\n']

Здесь Python проходит по всем строкам переданной коллекции и записывает каждую из них в файл.

Также метод file.writelines(), как и метод file.write(), не добавляет символы перевода строки (\n) между элементами или после них. Если необходимо, чтобы каждый элемент коллекции записывался с новой строки, следует убедиться, что символ \n уже присутствует в конце каждой строки в самой коллекции.

Примеры

Пример 1. Создание резервной копии

Исходный файл data_source.txt создан следующим образом:

with open("data_source.txt", "w", encoding="utf-8") as data_file:
    data_file.write("Важные данные для архивирования. \nСтрока 2.\n")

Его резервная копия создаётся путём чтения его содержимого и последующей записи его в новый файл data_backup.txt:

# Читаем содержимое исходного файла
with open("data_source.txt", "r", encoding="utf-8") as data_file:
    file_content = data_file.read()

# Записываем его в резервный файл
with open("data_backup.txt", "w", encoding="utf-8") as backup_file:
    backup_file.write(file_content)

# Проверяем содержимого резервного файла
with open("data_backup.txt", "r", encoding="utf-8") as backup_file:
    backup_content = backup_file.read()
    
# Сравниваем
is_identical = (file_content == backup_content)
print(f"Копия идентична оригиналу? {is_identical}")

Вывод:

Копия идентична оригиналу? True

Пример 2. Наличие блюд в меню

Информация о наличии блюд в ресторане хранится в списке кортежей products. Для записи этих данных в файл menu.txt они предварительно преобразуются в список строк нужного формата:

products = [
    ("Салат с хурмой", "В наличии"),
    ("Простые снеки из тыквы", "Нет в наличии"),
    ("Крем-суп с баклажанами", "В наличии"),
    ("Грузинский салат с орехами", "Снят с продажи")
]

# Преобразуем список кортежей в список строк с нужным форматированием
formatted_lines = [f"{name}: {status}\n" for name, status in products]

# Записываем строки в файл
with open("menu.txt", "w", encoding="utf-8") as menu_file:
    # Записываем все строки из списка за один вызов
    menu_file.writelines(formatted_lines)

# Читаем полученный файл
with open("menu.txt", "r", encoding="utf-8") as menu_file:
    print(menu_file.read())

Вывод:

Салат с хурмой: В наличии
Простые снеки из тыквы: Нет в наличии
Крем-суп с баклажанами: В наличии    
Грузинский салат с орехами: Снят с продажи

Пример 3. Тестирование чтения большого файла

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

# Создаем пример большого файла (10000 строк)
with open("large_data.txt", "w", encoding="utf-8") as data_file:
    for i in range(10000):
        data_file.write(f"Строка данных №{i+1}\n")

line_count = 0
total_char_count = 0
with open("large_data.txt", "r", encoding="utf-8") as data_file:
    for line in data_file:
        line_count += 1
        total_char_count += len(line) 

        # Выводим прогресс для демонстрации
        if line_count % 2000 == 0:
             print(f"Обработано {line_count} строк...")
             
print(f"Всего строк в файле: {line_count}")
print(f"Общее количество символов: {total_char_count}")

Вывод:

Обработано 2000 строк...
Обработано 4000 строк...
Обработано 6000 строк...
Обработано 8000 строк...
Обработано 10000 строк...        
Всего строк в файле: 10000       
Общее количество символов: 508894

Итоги

  • По способу хранения информации все файлы делятся на текстовые и бинарные (двоичные).
  • Функция open() открывает файл и возвращает файловый объект.
  • По умолчанию файл открывается в режиме чтения ("r"), но также существуют режимы для записи ("w") и дозаписи ("a"), которые можно совмещать с чтением с помощью символа +.
  • Режимы "w" и "w+" используются, когда нужно создать новый файл или полностью перезаписать содержимое существующего.
  • Режимы "a" и "a+" используются, когда нужно добавить новые данные в конец файла, сохраняя при этом старые данные.
  • Для работы с бинарными файлами к режиму добавляется буква "b", например, "rb" или "wb".
  • В конце работы с файлом его необходимо закрыть с помощью метода file.close(). Но более предпочтительным методом является использование контекстного менеджера with open(...) as имя_объекта_с_файлом, который гарантирует вызов этого метода автоматически вне зависимости от того, как завершился блок кода with.
  • Метод file.read() считывает все строки файла в одну строку, а метод file.readlines() – в список.
  • Метод file.readline() считывает одну строку из файла. Наиболее распространённым способом построчного чтения файла является перебор его в цикле for.
  • Метод file.write() записывает строку в начало (режимы "r+", "w" и "w+") или конец (режимы "a" и "a+") файла.
  • Метод file.writelines() записывает в файл последовательность (список или кортеж) строк.

Задания для самопроверки

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

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

2. В чём главное отличие режима "a" от режима "w" при работе с существующим файлом?

При открытии в режиме "a" указатель устанавливается в конец файла и его содержимое не перезаписывается. Режим "w" же полностью стирает содержимое файла и устанавливает указатель в его начало.

3. Создайте файл notes.txt, открыв его в режиме записи с кодировкой "utf-8", и запишите в него строки "Первая заметка\n" и "Вторая заметка\n".

with open("notes.txt", "w", encoding="utf-8") as notes_file:
    notes_file.write("Первая заметка\n")
    notes_file.write("Вторая заметка\n")

4. Откройте файл notes.txt из предыдущего задания в режиме чтения и выведите на экран список строк этого файла.

with open("notes.txt", "r", encoding="utf-8") as notes_file:
    lines = notes_file.readlines()

print(lines)
# Вывод: ['Первая заметка\n', 'Вторая заметка\n']

5. Программно создайте файл prices.txt, где каждая строка содержит одно число:

15.5
22.3
10.0

Откройте этот файл в режиме чтения, прочитайте и преобразуйте каждую строку в число с плавающей точкой (float) и вычислите среднее арифметическое всех цен, которое выведите на экран.

# Создание файла prices.txt
with open("prices.txt", "w") as prices_file:
    prices_file.writelines(["15.5\n", "22.3\n", "6.0\n"])

# Чтение и вычисление
with open("prices.txt", "r") as prices_file:
    numbers = [float(n) for n in prices_file.readlines()]
    average = sum(numbers) / len(numbers)
    print(average)
    # Вывод: 6