Сохранение и загрузка из JSON
Сохранение и загрузка из JSON
Данные в памяти исчезают при закрытии программы. Чтобы телефонная книга была полезной, контакты нужно сохранять на диск. В этом уроке реализуем методы save() и _load() в классе AddressBook.
Проблема: объекты нельзя напрямую сериализовать
json.dump умеет работать со словарями, списками, строками и числами. Но наши объекты Contact — это пользовательские классы. Попытка сохранить список Contact напрямую даст TypeError:
import json
from contact import Contact
contacts = [Contact("Алиса", "+7-999-111")]
# json.dumps(contacts) # TypeError: Object of type Contact is not JSON serializable
Решение: конвертировать объект в словарь (сериализация) и обратно (десериализация).
Сериализация Contact в словарь
Добавим метод to_dict() и фабричный метод from_dict() в класс Contact:
# contact.py — дополнение к классу Contact
class Contact:
def __init__(self, name, phone, email=None):
# ... как раньше
def to_dict(self) -> dict:
"""Конвертировать контакт в словарь для JSON."""
return {
"name": self.name,
"phone": self.phone,
"email": self.email,
}
@classmethod
def from_dict(cls, data: dict) -> "Contact":
"""Создать Contact из словаря (загруженного из JSON)."""
return cls(
name=data["name"],
phone=data["phone"],
email=data.get("email"),
)
@classmethod — декоратор, который делает метод привязанным к классу, а не к экземпляру. Первый аргумент cls — сам класс (аналог self для обычных методов). from_dict — фабричный метод: создаёт объект из данных другого формата.
Реализация save() и _load()
# address_book.py
import json
from pathlib import Path
from contact import Contact, ContactNotFoundError
class AddressBook:
def __init__(self, filepath):
self.filepath = Path(filepath)
self._contacts = []
self._load()
def save(self) -> None:
"""Сохранить все контакты в JSON-файл."""
data = [c.to_dict() for c in self._contacts]
self.filepath.write_text(
json.dumps(data, ensure_ascii=False, indent=2),
encoding="utf-8"
)
def _load(self) -> None:
"""Загрузить контакты из JSON-файла (при старте)."""
if not self.filepath.exists():
return # файла нет — начинаем с пустой книги
try:
text = self.filepath.read_text(encoding="utf-8")
data = json.loads(text)
self._contacts = [Contact.from_dict(item) for item in data]
except (json.JSONDecodeError, KeyError, ValueError):
print(f"Предупреждение: не удалось загрузить {self.filepath}, начинаем заново")
self._contacts = []
Автосохранение после изменений
Сохранять вручную — риск забыть. Лучший паттерн — сохранять сразу после каждого изменения:
class AddressBook:
def add(self, contact):
for i, existing in enumerate(self._contacts):
if existing.name.lower() == contact.name.lower():
self._contacts[i] = contact
self.save() # автосохранение
return
self._contacts.append(contact)
self.save() # автосохранение
def delete(self, name):
contact = self.find(name)
self._contacts.remove(contact)
self.save() # автосохранение
Альтернативный подход — явный вызов book.save() после всех операций. Для нашего проекта автосохранение удобнее: пользователь не потеряет данные при неожиданном завершении программы.
Проверка: данные сохраняются между запусками
# Первый запуск:
from address_book import AddressBook
from contact import Contact
book = AddressBook("contacts.json")
book.add(Contact("Алиса", "+7-999-111-22-33"))
print("Сохранено")
# Второй запуск:
book2 = AddressBook("contacts.json")
for c in book2.all():
print(c)
# Алиса: +7-999-111-22-33
После второго запуска данные загрузились из contacts.json. Файл выглядит так:
[
{
"name": "Алиса",
"phone": "+7-999-111-22-33",
"email": null
}
]
Итог
to_dict()— объект → словарь для сериализации.@classmethod from_dict(cls, data)— словарь → объект; фабричный метод.save()использует list comprehension:[c.to_dict() for c in self._contacts]._load()обрабатывает отсутствие файла и повреждённые данные без падения.- Автосохранение в
add()иdelete()защищает от потери данных.
В следующем уроке реализуем CLI-интерфейс — меню в цикле для взаимодействия с пользователем.