Усі три рівні — про одну програму: маленький облік бібліотеки. Наскрізний приклад лекції (продажі) ми лишаємо позаду навмисно: клас треба вміти описати для своєї предметної області, а не лише повторити чужий.
Працюй у звичайному файлі biblioteka.py, а перевірки пиши поруч, унизу того самого
файлу, під захисною умовою:
if __name__ == "__main__":
# тут усі assert-и
...
Тоді файл можна і запускати (python3 biblioteka.py), і імпортувати з іншого місця.
Якщо жоден assert не спрацював, програма закінчується мовчки — це і є «зроблено».
рівень 1Рівень 1 — База
Напиши клас Книга з полями назва, автор, рік, примірників і методом
доступна(), що повертає True, якщо примірників більше нуля.
Обовʼязково:
- усі перевірки — в
__init__, до присвоєнь: порожня назва, рік менший за 1450 або більший за 2100, відʼємна кількість примірників — цеValueErrorіз повідомленням українською; __repr__, з якого видно назву, автора й рік;- жодного змінюваного значення в тілі класу.
Зроблено, якщо цей блок перевірок проходить без жодного спрацювання:
книга = Книга("Кобзар", "Шевченко", 1840, 3)
assert книга.доступна() is True
assert repr(книга) == "Книга('Кобзар', 'Шевченко', 1840)" # свій формат — свій рядок
for погані in [("", "Шевченко", 1840, 3), ("Кобзар", "Шевченко", 1200, 3),
("Кобзар", "Шевченко", 1840, -1)]:
try:
Книга(*погані)
except ValueError:
pass
else:
raise AssertionError(f"перевірка не спрацювала: {погані}")
рівень 2Рівень 2 — Плюс
Додай клас Бібліотека, який зберігає книжки й веде журнал подій.
- У
__init__створи два поля:self.книжки = []іself.журнал = []. - Метод
додати(книга)— з перевіркоюisinstance(книга, Книга), інакшеTypeError. Кожне додавання пише рядок уself.журнал. - Метод
видати(назва)— зменшуєпримірниківна одиницю й повертаєTrue; якщо книжки немає або примірників нуль, повертаєFalse. Обидва випадки — у журнал. - Атрибут класу
максимум_на_руки = 5(незмінне число!) і метод, який ним користується. __repr__для бібліотеки: скільки назв і скільки примірників усього.
Зроблено, якщо:
- дві бібліотеки не бачать журналів одна одної — це доводить
assert:
python
а, б = Бібліотека(), Бібліотека()
а.додати(Книга("Кобзар", "Шевченко", 1840, 1))
assert len(а.журнал) == 1 and len(б.журнал) == 0
assert а.журнал is not б.журнал
а.видати("Кобзар")двічі поспіль дає спершуTrue, потімFalse, і в журналі зʼявилось два різні рядки;а.додати("Кобзар")(рядок замість книжки) кидаєTypeError;- після
а.максимум_на_руки = 2значення в самому класі лишається5. Перевір цеassert Бібліотека.максимум_на_руки == 5і поясни в коментарі одним реченням, чому так сталося.
рівень 3Рівень 3 — Виклик
Три задачі. Кожну можна зробити незалежно.
А. Пастка своїми руками. Свідомо зроби помилку: перенеси журнал = [] з __init__
у тіло класу Бібліотека. Напиши тест, який цю помилку ловить, — такий, що падає на
зіпсованій версії й проходить на правильній. Поверни правильний варіант і залиш тест
у файлі.
Б. Порахуй ціну гнучкості. Створи 200 000 обʼєктів Книга і заміряй памʼять
(tracemalloc.get_traced_memory() до і після). Потім додай у клас
__slots__ = ("назва", "автор", "рік", "примірників") і повтори замір.
В. Кому цей клас не потрібен. Знайди у власному коді (або напиши навмисно) клас,
який складається лише з __init__ і методів отримати_X / встановити_X. Перепиши той
самий сенс без класу.
Зроблено, якщо:
- А: тест справді розрізняє дві версії — покажи це, запустивши його двічі й зафіксувавши результат у коментарі («на версії з журналом у тілі класу падає на рядку N»);
- Б: маєш два числа й відношення між ними, а також рядок, який доводить, що після
__slots__одруківка більше не проходить мовчки:
python
try:
книга.примірникив = 3 # одруківка
except AttributeError:
print("✅ __slots__ ловить одруківку")
- В: нова версія коротша за стару, і ти можеш назвати одним реченням, що саме зникло разом із класом (і чого не зникло).
Підказки
- Перевірки в
__init__пиши до першого присвоєння. Якщо половину полів уже записано, а потім кинуто виняток, обʼєкт нікуди не потрапить — але звичка ставити перевірки першими рятує від напівзаповнених обʼєктів у складніших випадках. - Не можеш вирішити, метод це чи функція? Спитай себе: чи потрібен йому
self. Якщо всередині жодного разу не згадуєтьсяself, це, найімовірніше, звичайна функція. - Найшвидший спосіб побачити, що насправді лежить в обʼєкті, — надрукувати
vars(обʼєкт). Це той самий__dict__, тільки коротше писати. - Щоб перевірити, чи атрибут спільний, не рахуй елементи — питай про ідентичність:
а.журнал is б.журнал. Довжини можуть збігтися випадково,isне збіжиться ніколи. - Формат
__repr__вибираєш ти сам — але візьми за орієнтир рядок, схожий на виклик, яким такий обʼєкт можна створити знову. Тоді свій жеassertнаreprписати легко.