# Домашнє завдання · Помилки та винятки

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

Заведи для цього завдання окрему теку — хай буде `dz18/` — і працюй у ній.
Скрізь, де просять «навмисно зламати», зберігай **повний traceback** текстом:
він і є доказом того, що ти зробив.

---

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

Напиши файл `kalkulyator.py` з функцією `podilyty(a, b)`, яка повертає `a / b`.

1. Обгорни ділення в `try` / `except` **із конкретним типом**: при діленні на нуль
   функція має повертати рядок `"на нуль ділити не можна"`, а не падати.
2. Додай другу гілку `except TypeError`, яка повертає `"аргументи мають бути числами"` —
   вона спрацює, якщо передати рядок.
3. Додай блок `finally`, який дописує `+1` до глобального лічильника `викликів`.
4. У кінці файлу, під `if __name__ == "__main__":`, постав перевірки:

   ```python
   assert podilyty(10, 2) == 5.0
   assert podilyty(10, 0) == "на нуль ділити не можна"
   assert podilyty("десять", 2) == "аргументи мають бути числами"
   assert викликів == 3, "finally мав спрацювати на кожен виклик"
   print("✅ рівень 1")
   ```

**Зроблено, якщо:** `python3 kalkulyator.py` друкує `✅ рівень 1` і більше нічого;
у файлі немає жодного голого `except:` і жодного `except Exception`.

---

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

Візьми битий прайс і доведи на числах, чим голий `except` шкідливий.

1. Створи файл `prays.txt` із **шістьма** рядками формату `назва;ціна`, з яких
   три зіпсовані по-різному: ціна словами, немає крапки з комою, порожня назва.
   (Читати файл ще не вчились — просто поклади ті самі рядки в список у коді,
   а `prays.txt` хай буде довідкою для тебе.)
2. Напиши два варіанти обробника прайсу в одному файлі `porivnyannya.py`:
   * `rozibraty_goliy(ryadok)` — з `except:` без типу;
   * `rozibraty_tochno(ryadok)` — з `except (ValueError, IndexError)`.
3. Тепер зроби в **обох** функціях однаковий одрук: напиши `flot` замість `float`.
   Запусти обидві на всьому прайсі й запиши в `sposterezhennya.md`:
   * що надрукувала кожна версія;
   * яку суму дала кожна;
   * traceback, який показала точна версія.
4. Поверни `float` на місце й переконайся, що тепер обидві версії дають **однакову**
   суму. Додай `assert`, який це фіксує.

**Зроблено, якщо:** у `sposterezhennya.md` є (а) дві різні суми з одруком,
(б) traceback зі словом `NameError`, (в) абзац своїми словами про те, чому
різниця в одному слові після `except` коштує стільки; а `python3 porivnyannya.py`
у виправленій версії відпрацьовує без помилок і друкує пройдений `assert`.

---

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

Побудуй власну ієрархію винятків і доведи, що вона поводиться як дерево.

1. У файлі `vynyatky.py` опиши три типи:

   ```
   PomylkaPraysu(Exception)
   ├── PomylkaCiny      — ціна не число або відʼємна
   └── PomylkaFormatu   — рядок не розкладається рівно на дві частини
   ```

2. У файлі `rozbir.py` напиши `rozibraty(ryadok)`, яка кидає **точний** підтип
   у кожному випадку й обовʼязково зберігає первинну причину через `from e`.
3. Доведи `assert`-ами всі чотири твердження:
   * `except PomylkaCiny` ловить помилку ціни й **не ловить** помилку формату;
   * `except PomylkaPraysu` ловить обидві;
   * `except Exception` теж ловить обидві;
   * `except KeyboardInterrupt` не ловить жодної.
4. Заміряй довжину traceback у рядках для трьох варіантів підняття на **одному й тому
   самому** зіпсованому рядку: `raise X(...)`, `raise X(...) from e`,
   `raise X(...) from None`. Підказка: `traceback.format_exc()` повертає звіт рядком.
   Запиши три числа в `tracebacks.md` і поясни, чому два з них однакові, а третє менше.
5. Додай у `rozbir.py` функцію `obrobyty(ryadky)`, яка повертає три списки:
   `ciny`, `skargy` (очікувані `PomylkaPraysu`) і `avarii` (усе інше). Перевір її на списку,
   де один елемент — не рядок, а число `42`, і доведи `assert`-ом, що це число
   опинилось саме в `avarii`.

**Зроблено, якщо:** `python3 rozbir.py` відпрацьовує без помилок; усі `assert`
з пунктів 3 і 5 мовчать; у `tracebacks.md` є три числа й абзац пояснення,
у якому названо, що саме додає до звіту неявний ланцюжок.

---

## Підказки

- **Не вгадуй тип винятку — спитай.** Обгорни підозрілий рядок у
  `try: ... except BaseException as e: print(type(e).__name__, e)`. Це разова
  діагностика, а не код, який лишається: дізнався тип — заміни на конкретний.
- **`float("28.5")` і `float(None)` падають по-різному.** Перший дає `ValueError`
  (рядок є, але не схожий на число), другий — `TypeError` (рядка немає взагалі).
  У завданні другого рівня це пригодиться.
- **Щоб `finally` було видно**, друкуй у ньому рядок або дописуй у список. Інакше
  легко переконати себе, що він виконався, не перевіривши цього.
- **Власний виняток успадковуй від `Exception`, не від `BaseException`.** Інакше
  твоя біда поведеться як Ctrl+C і проскочить повз усі чужі обробники.
- **Якщо `assert` мовчить — це успіх.** Він друкує щось лише тоді, коли все погано.
  Тому наприкінці ставлять `print("✅ …")`: інакше не видно, що перевірка взагалі була.
- Файли `__pycache__` у роботу не здаються.
