# Домашнє завдання · Наслідування й композиція

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

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

---

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

Побудуй власну гілку ієрархії на три рівні й доведи, що ланцюжок `super()` працює
наскрізь.

1. Візьми з практики базовий клас `Видання` (назва, рік, методи `опис` і `вік`).
2. Додай `Журнал(Видання)` з номером випуску. Його `опис` має **кликати
   `super().опис()`**, а не складати рядок з нуля.
3. Додай `Стаття(Журнал)` з автором і сторінкою. Її `опис` теж кличе `super().опис()`.
4. Створи одну статтю й надрукуй: її `опис()`, її `__dict__` і `Стаття.__mro__`.

**Зроблено, якщо:**

- у файлі є три `assert`и, які по одному перевіряють наявність у рядку `опис()`
  внеску кожного з трьох класів (назва+рік, номер, автор) — і всі проходять;
- `assert [к.__name__ for к in Стаття.__mro__] == ["Стаття", "Журнал", "Видання", "object"]`
  проходить;
- `assert len(стаття.__dict__) == 5` (назва, рік, номер, автор, сторінка) проходить —
  тобто жодна ланка ланцюжка не загубилась.

---

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

Та сама предметна область, але тепер вимога змінюється — і треба показати, що
наслідування її не тримає, а композиція тримає.

1. **Спершу наслідуванням.** Напиши три класи: `ПаперовеВидання(Видання)`,
   `АудіоВидання(Видання)`, `ЕлектроннеВидання(Видання)`, у кожного свій
   `як_видати()`. Створи по одному обʼєкту.
2. **Тепер нова вимога:** «книга, яку читач узяв на папері, наступного тижня має
   стати доступною ще й файлом — той самий обʼєкт каталогу, той самий інвентарний
   номер». Спробуй виконати її, не створюючи новий обʼєкт. Запиши в коментарі, чому
   не виходить.
3. **Перепиши на композицію.** Один клас `Позиція` (назва, рік, автор, `носій`,
   `інвентарний_номер`) і три маленькі класи-носії `Папір`, `Аудіо`, `Файл`, у кожного
   свій `як_видати()`. `Позиція.як_видати()` делегує роботу носію.
4. Заведи одну позицію на папері, запамʼятай її `id()` та інвентарний номер,
   зміни носій на `Файл()` і перевір те й те ще раз.

**Зроблено, якщо:**

- у файлі є `assert id(позиція) == id_до` і `assert позиція.інвентарний_номер == номер_до` —
  доказ, що це той самий обʼєкт;
- є `assert позиція.як_видати() != видача_до` — доказ, що поведінка змінилася;
- є `assert type(позиція) is Позиція` — доказ, що клас обʼєкта не змінювався;
- у коментарі до пункту 2 своїми словами написано, чому клас обʼєкта в Python
  змінити не можна й до чого призводить обхідний шлях «створити новий обʼєкт».

---

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

Реалізуй пошук методу самостійно й знайди межу, за якою Python здається.

1. Напиши функцію `мій_mro(клас)`, яка будує лінеаризацію **самостійно**, без
   звертання до `клас.__mro__`. Дозволено користуватися лише `клас.__bases__`.
   Три правила лінеаризації: клас іде раніше за будь-якого свого предка; предки
   зберігають той порядок, у якому записані в дужках; кожен клас у списку рівно один раз.
2. Перевір `мій_mro` щонайменше на пʼятьох ієрархіях: лінійний ланцюжок із трьох
   класів, ромб, «подвійний ромб» (два ромби один над одним), клас без явних
   базових, і клас, що успадковує два незалежні дерева.
3. Знайди ієрархію, для якої Python **відмовляється** будувати MRO, і спіймай
   `TypeError` із текстом про неможливість лінеаризації. Поясни в коментарі,
   яке саме з трьох правил у ній суперечить іншому.
4. На основі `мій_mro` напиши `мій_getattr(обʼєкт, імʼя)`, яка шукає атрибут спершу
   в `обʼєкт.__dict__`, потім по класах, і кидає `AttributeError` із таким самим
   текстом, як у Python.

**Зроблено, якщо:**

- `assert мій_mro(К) == list(К.__mro__)` проходить для всіх пʼятьох ієрархій;
- зафіксовано ієрархію, на якій `class` падає з `TypeError: Cannot create a consistent
  method resolution order (MRO)`, і в коментарі пояснено конфлікт правил;
- `мій_getattr` дає той самий результат, що й `getattr`, щонайменше на десяти парах
  «обʼєкт + імʼя», включно з двома неіснуючими іменами (де обидві функції мусять
  кинути `AttributeError`).

---

## Підказки

- **Рівень 1.** Якщо в `опис()` статті зник рік — перевір, чи `Журнал.опис` справді
  кличе `super()`, а не складає рядок сам. Ланцюжок рветься в найтихішому місці.
- **Рівень 2.** Питання «чому не виходить» має просту відповідь у два слова: подивись,
  що поверне `type(обʼєкт)` до й після будь-яких твоїх спроб.
- **Рівень 3.** Лінеаризація будується не обходом углиб. Візьми списки-кандидати
  (сам клас, потім `мій_mro` кожного предка, потім список самих предків) і на кожному
  кроці бери **голову першого списку, якої немає в хвості жодного іншого**. Якщо такої
  голови немає в жодному списку — це і є та сама суперечність із пункту 3.
- Перевіряти зручно на класах-пустишках: `class A: pass`, `class B(A): pass` — імена
  коротші, а механізм той самий.
