Працюємо з тим самим каталогом бібліотеки, що й у лекції. Усе роби в окремому файлі
каталог.py або в новому зошиті — практику не редагуй, вона знадобиться для звірки.
рівень 1Рівень 1 — База
Побудуй власну гілку ієрархії на три рівні й доведи, що ланцюжок super() працює
наскрізь.
- Візьми з практики базовий клас
Видання(назва, рік, методиописівік). - Додай
Журнал(Видання)з номером випуску. Йогоописмає кликатиsuper().опис(), а не складати рядок з нуля. - Додай
Стаття(Журнал)з автором і сторінкою. Їїопистеж кличеsuper().опис(). - Створи одну статтю й надрукуй: її
опис(), її__dict__іСтаття.__mro__.
Зроблено, якщо:
- у файлі є три
assertи, які по одному перевіряють наявність у рядкуопис()внеску кожного з трьох класів (назва+рік, номер, автор) — і всі проходять; assert [к.__name__ for к in Стаття.__mro__] == ["Стаття", "Журнал", "Видання", "object"]проходить;assert len(стаття.__dict__) == 5(назва, рік, номер, автор, сторінка) проходить — тобто жодна ланка ланцюжка не загубилась.
рівень 2Рівень 2 — Плюс
Та сама предметна область, але тепер вимога змінюється — і треба показати, що наслідування її не тримає, а композиція тримає.
- Спершу наслідуванням. Напиши три класи:
ПаперовеВидання(Видання),АудіоВидання(Видання),ЕлектроннеВидання(Видання), у кожного свійяк_видати(). Створи по одному обʼєкту. - Тепер нова вимога: «книга, яку читач узяв на папері, наступного тижня має стати доступною ще й файлом — той самий обʼєкт каталогу, той самий інвентарний номер». Спробуй виконати її, не створюючи новий обʼєкт. Запиши в коментарі, чому не виходить.
- Перепиши на композицію. Один клас
Позиція(назва, рік, автор,носій,інвентарний_номер) і три маленькі класи-носіїПапір,Аудіо,Файл, у кожного свійяк_видати().Позиція.як_видати()делегує роботу носію. - Заведи одну позицію на папері, запамʼятай її
id()та інвентарний номер, зміни носій наФайл()і перевір те й те ще раз.
Зроблено, якщо:
- у файлі є
assert id(позиція) == id_доіassert позиція.інвентарний_номер == номер_до— доказ, що це той самий обʼєкт; - є
assert позиція.як_видати() != видача_до— доказ, що поведінка змінилася; - є
assert type(позиція) is Позиція— доказ, що клас обʼєкта не змінювався; - у коментарі до пункту 2 своїми словами написано, чому клас обʼєкта в Python змінити не можна й до чого призводить обхідний шлях «створити новий обʼєкт».
рівень 3Рівень 3 — Виклик
Реалізуй пошук методу самостійно й знайди межу, за якою Python здається.
- Напиши функцію
мій_mro(клас), яка будує лінеаризацію самостійно, без звертання доклас.__mro__. Дозволено користуватися лишеклас.__bases__. Три правила лінеаризації: клас іде раніше за будь-якого свого предка; предки зберігають той порядок, у якому записані в дужках; кожен клас у списку рівно один раз. - Перевір
мій_mroщонайменше на пʼятьох ієрархіях: лінійний ланцюжок із трьох класів, ромб, «подвійний ромб» (два ромби один над одним), клас без явних базових, і клас, що успадковує два незалежні дерева. - Знайди ієрархію, для якої Python відмовляється будувати MRO, і спіймай
TypeErrorіз текстом про неможливість лінеаризації. Поясни в коментарі, яке саме з трьох правил у ній суперечить іншому. - На основі
мій_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— імена коротші, а механізм той самий.