Реализация: классы Contact и AddressBook
Реализация: классы Contact и AddressBook
Скелет проекта готов. Пора наполнить классы реальной логикой. В этом уроке реализуем Contact и AddressBook полностью — без заглушек pass.
Класс Contact
Contact — простой класс-контейнер данных. Его задача: хранить поля и уметь представить себя строкой:
# contact.py
class ContactNotFoundError(Exception):
"""Выбрасывается когда контакт не найден в AddressBook."""
pass
class Contact:
def __init__(self, name: str, phone: str, email: str = None):
if not name.strip():
raise ValueError("Имя не может быть пустым")
if not phone.strip():
raise ValueError("Телефон не может быть пустым")
self.name = name.strip()
self.phone = phone.strip()
self.email = email.strip() if email else None
def __str__(self):
line = f"{self.name}: {self.phone}"
if self.email:
line += f" ({self.email})"
return line
def __repr__(self):
return f"Contact({self.name!r}, {self.phone!r}, email={self.email!r})"
def __eq__(self, other):
if not isinstance(other, Contact):
return NotImplemented
return self.name.lower() == other.name.lower()
Ключевые решения:
name.strip()— убираем лишние пробелы при создании__eq__сравнивает без учёта регистра — «Алиса» и «алиса» — один контактemailнеобязательный (значение по умолчаниюNone)
Класс AddressBook: поиск и удаление
# address_book.py
from contact import Contact, ContactNotFoundError
class AddressBook:
def __init__(self, filepath: str):
self.filepath = filepath
self._contacts: list = []
self._load()
def add(self, contact: Contact) -> None:
"""Добавить контакт. Если имя уже есть — заменить."""
for i, existing in enumerate(self._contacts):
if existing.name.lower() == contact.name.lower():
self._contacts[i] = contact
return
self._contacts.append(contact)
def find(self, name: str) -> Contact:
"""Найти контакт по имени (без учёта регистра)."""
name_lower = name.lower()
for contact in self._contacts:
if contact.name.lower() == name_lower:
return contact
raise ContactNotFoundError(f"Контакт '{name}' не найден")
def delete(self, name: str) -> None:
"""Удалить контакт по имени."""
contact = self.find(name) # выбросит ContactNotFoundError если нет
self._contacts.remove(contact)
def all(self) -> list:
"""Вернуть все контакты, отсортированные по имени."""
return sorted(self._contacts, key=lambda c: c.name.lower())
def search(self, query: str) -> list:
"""Поиск по частичному совпадению имени или телефона."""
q = query.lower()
return [
c for c in self._contacts
if q in c.name.lower() or q in c.phone
]
def _load(self) -> None:
pass # реализуем в следующем уроке
def save(self) -> None:
pass # реализуем в следующем уроке
Тестирование в main.py
Проверим, что классы работают правильно, прежде чем добавлять сохранение:
# main.py (временно — для проверки)
from contact import Contact, ContactNotFoundError
from address_book import AddressBook
book = AddressBook("contacts.json")
book.add(Contact("Алиса", "+7-999-111-22-33", "alice@example.com"))
book.add(Contact("Борис", "+7-999-444-55-66"))
book.add(Contact("Вера", "+7-999-777-88-99", "vera@example.com"))
for c in book.all():
print(c)
print()
try:
found = book.find("Борис")
print(f"Найден: {found}")
book.delete("Борис")
print(f"После удаления: {len(book.all())} контактов")
except ContactNotFoundError as e:
print(f"Ошибка: {e}")
Вывод:
Алиса: +7-999-111-22-33 (alice@example.com)
Борис: +7-999-444-55-66
Вера: +7-999-777-88-99 (vera@example.com)
Найден: Борис: +7-999-444-55-66
После удаления: 2 контактов
Итог
Contactхранит имя, телефон, email; валидирует в__init__; умеет красиво выводиться.AddressBookхранит список контактов; поддерживаетadd,find,delete,all,search.find()выбрасываетContactNotFoundErrorвместо возвратаNone— вызывающий код явно обрабатывает ошибку.add()заменяет контакт при совпадении имени — нет дубликатов.
В следующем уроке реализуем _load() и save() — сохранение данных в JSON между сессиями.