# Домашнє завдання · Типи (type hints)

> Лекція: [lecture.html](lecture.html) · Практика: [practice.ipynb](practice.ipynb)

Працюємо з тим самим журналом продажів, що в лекції й практиці. Усі три рівні —
в одному файлі `журнал.py`, який має запускатися звичайним `python3 журнал.py`.

Якщо в тебе є можливість поставити перевірювач типів — постав:

```bash
pip install mypy
mypy журнал.py
```

Якщо ні, нічого страшного: кожен рівень має критерій, який перевіряється
`assert`-ами й друком, без жодних додаткових інструментів.

---

## 🟢 Рівень 1 — База

Опиши свій маленький журнал і анотуй його повністю.

1. Створи `@dataclass` на свою тему (не продажі): книжки в бібліотеці, треки в
   плейлисті, витрати за тиждень — що завгодно з **чотирма** полями, серед яких
   є хоча б один список.
2. Напиши дві функції з повними анотаціями параметрів і результату:
   одну, що рахує число (сума, середнє, кількість), і одну, що повертає список.
3. Надрукуй `__annotations__` обох функцій і `__annotations__` свого класу.
4. Порушь одну анотацію навмисно — передай у параметр `int` рядок — і покажи
   `print`-ом, що програма не впала.

**Зроблено, якщо:** файл запускається без помилок; у виводі видно три словники
анотацій, і в словнику функції є ключ `'return'`; є рядок виводу, який доводить,
що виклик із неправильним типом відпрацював без винятку.

---

## 🟡 Рівень 2 — Плюс

Навчися описувати відсутність значення й вкладені структури.

1. Напиши функцію пошуку з підписом `-> ТвійКлас | None`, яка повертає `None`,
   коли нічого не знайшла.
2. Напиши функцію, яка **використовує** результат пошуку й обробляє обидві гілки
   через `if ... is None`. Перевір `assert`-ами обидва випадки: знайшли й не знайшли.
3. Зроби функцію, яка групує записи у структуру `dict[str, list[ТвійКлас]]`, і
   дай цьому типу псевдонім з осмисленим імʼям.
4. Додай короткий коментар (2–3 речення) про те, що станеться, якщо прибрати
   перевірку `is None`: яка помилка, у якому рядку й на яких саме даних.

**Зроблено, якщо:** обидва `assert`-и проходять; у файлі є рядок-псевдонім типу,
і він використаний хоча б у двох підписах; коментар називає конкретний виняток
(`AttributeError`) і умову, за якої він виникає.

---

## 🔴 Рівень 3 — Виклик

Доведи власну межу між перевіркою до запуску й перевіркою під час виконання.

1. Візьми `перевірити_виклик` із практики й навчи його розуміти `X | None`.
   Підказка нижче.
2. Додай другу можливість: якщо анотація — параметризований контейнер
   (`list[int]`, `dict[str, int]`), перевіряй не лише сам контейнер, а й тип
   **першого** елемента.
3. Склади таблицю з шести-восьми викликів: що каже твій перевірювач і що сказав би
   mypy (це можна виписати з лекції). Знайди хоча б **два** випадки, де відповіді
   розходяться, і поясни кожен одним реченням.
4. Заверши висновком: назви одну річ, яку перевірка під час виконання робить краще
   за mypy, і одну, яку вона не зможе зробити ніколи.

**Зроблено, якщо:** виклик із `None` у параметр `int | None` не дає скарг, а виклик
із рядком у той самий параметр — дає; у таблиці є щонайменше два розходження з
поясненнями; висновок називає конкретні приклади, а не загальні слова.

---

## Підказки

- Дістати складові з `int | None` можна так: `typing.get_origin(анотація)` скаже,
  що це обʼєднання, а `typing.get_args(анотація)` поверне кортеж варіантів
  `(int, NoneType)`. Для `list[int]` ті самі функції дадуть `list` і `(int,)`.
- `isinstance` не приймає параметризований тип — це не помилка твого коду, а
  свідоме обмеження мови. Перевіряй окремо контейнер (`get_origin`) і окремо
  елемент.
- Порожній контейнер перевірити нічим: у `[]` немає першого елемента. Вирішити цю
  ситуацію можна двома способами — вважати її правильною або скаржитись; обери
  один і напиши в коментарі, чому саме він.
- Не намагайся зробити повноцінний mypy. Мета вправи протилежна: побачити, де
  саме перевірка під час виконання впирається у стелю.
