Работа с файлами. Сериализация: json и pickle#

Файловый объект. Функция open#

Для работы с файлами существует функция open, которая создаёт файловый объект. Единственный обязательный параметр этой функции path — путь к файлу. Очень важный опциональный параметр mode отвечает за режим открытия файла.

Символ

Значение

r

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

w

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

x

Создать файл и открыть на запись (ошибка, если файл существует)

a

Открыть файл на дозапись (если файл существует, то дозаписывать в конец файла)

b

Открыть как бинарный файл

t

Открыть как текстовый файл (по умолчанию)

+

Открыть на запись и чтение

По умолчанию функцию open открывает файл как текстовый для чтения. В качестве примера создадим файл "first_file.txt" в папке "tmp". Для этого сначала убедимся в том, что папка существует и создадим строку path, представляющую путь до будущего файла.

import os

folder = "tmp"
filename = "first_file.txt"
path = os.path.join(folder, filename)

os.makedirs(folder, exist_ok=True)

Теперь создадим файловый объект, запишем в него текст. Для этого необходимо открыть файл в режиме на запись в текстовом режиме (флаг "wt" или "w", т.к. текстовый режим подразумевается по умолчанию), записать строки текста в него методом write, а в конце обязательно закрыть файл методом close.

f = open(path, "w")

f.write(
"""Первая строка.
Вторая строка.
Последняя строка."""
)

f.close()

Теперь, чтобы убедиться в успешном создании файла, можно повторить эти операции, но на чтение. Как и прежде, в начале создаётся файловый объект встроенной функцией open и в конце закрыть его методом close, но в этот раз режим открытия на чтение, а значит в качестве параметра можно передать "rt", "r" или не передать ничего, так как по умолчанию подразумевается открытие на чтение в текстовом режиме. Считывать содержимое из файла можно несколькими методами, но для начала рассмотрим самый простой — чтение сразу всего содержимого методом read.

f = open(path)

content = f.read()
print(content)

f.close()
Первая строка.
Вторая строка.
Последняя строка.

Видим, что все операции завершились успешно.

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

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

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

with open(path) as name:
    первая строка блока with
    ...
    последняя строка блока with

первая инструкция после блока with # здесь файл гарантировано закрыт

В заголовке блока with открывается файл, и результирующий файловый объект связывается с именем name. Этот файл будет открыт до тех пор, пока программа не покинет следующий за заголовком блок кода. Как только программы этот блок покидает (естественным образом или в результате распространения исключения), файл автоматически закрывается и имя name освобождается.

Считаем содержимое созданного в предыдущем разделе файла с блоком with.

with open(filename) as f:
    print(f.read())
Первая строка.
Вторая строка.
Последняя строка.

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

Итерация по текстовому файлу#

Читать содержимое текстового файла можно несколько способами, кроме уже упомянутого метода read, есть также методы readline (чтение одной строки) и readlines (возвращает список всех строк файла). Но в подавляющем большинстве случаев содержимое файла считывают построчно, просто итерируясь по файловому объекту.

with open(filename) as f:
    for line in f:
        print(line)
Первая строка.

Вторая строка.

Последняя строка.

Видим, что при печати строки оказались разреженными. Дело в том, что при итерации в переменную line попадает вся считанная строка целиком, включая управляющий символ "\n", если он есть в конце. От этого можно избавиться методом rstrip.

Формат JSON#

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

JSON (англ. JavaScript Object Notation, обычно произносится как /ˈdʒeɪsən/ JAY-sən) — текстовый формат обмена данными, основанный на JavaScript. Как и многие другие текстовые форматы, JSON легко читается людьми. Источник: Wikipedia.

JSON изначально был придуман для сериализации объектов языка javascript, но со временем стал применяться и в других языках программирования. Его применение распространено и в python, т.к. две основные структуры данных в python — списки и словари — являются и основными блоками синтаксиса JSON.

За работу с JSON в python отвечает модуль стандартной библиотеки json. Основные методы этого модуля:

  • json.dumps сериализует объект в строку;

  • json.dump — то же самое, но сразу записывает в файл;

  • json.loads десериализует строку json и создаёт объект;

  • json.load десериализует содержимое файла json.

Рассмотрим самый простой пример с ними: создадим список и словарь и сериализуем их в строки.

import json

a_list = [1, 1., "a"]
json_list = json.dumps(a_list)

a_dict = {
    "key1": "value1",
    "key2": "value2"
    }
json_dict = json.dumps(a_dict)

print(f"json представление для списка: {json_list}")
print(f"json представление для словаря: {json_dict}")

Теперь исходные объекты можно восстановить обратным методом json.loads.

a_list_recovery = json.loads(json_list)
print(a_list_recovery, type(a_list_recovery))

a_dict_recovery = json.loads(json_dict)
print(a_dict_recovery, type(a_dict_recovery))

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

Коллекции в python могут быть вложенными друг в друга и json без проблем справляется с представлением таких вложенных структур.

import json
import os

course_description = {
    "lecturer": "Fadeev Egor",
    "course": "Computer technologies workshop",
    "topics": ["python3", "scientific libraries", "mathematical modeling"],
    "where": {
        "Faculty": "Faculty of physics",
        "room": "5-42"
    },
    "when": {
        "day of week": "Thursday",
        "time": "5:05 pm"
    },
    "number of students": 25
}

os.makedirs("tmp", exist_ok=True)
with open(os.path.join("tmp", "course_description.json"), "w") as f:
    json.dump(course_description, f, indent=4)

Если выполнить предыдущую ячейку, то в папке tmp должен создаться файл course_description.json со следующим содержимым (с точностью до порядка ключей).

{
    "lecturer": "Fadeev Egor",
    "course": "Computer technologies workshop",
    "topics": [
        "python3",
        "scientific libraries",
        "mathematical modeling"
    ],
    "where": {
        "Faculty": "Faculty of physics",
        "room": "5-42"
    },
    "when": {
        "day of week": "Thursday",
        "time": "5:05 pm"
    },
    "number of students": 25
}