Предметна область тут інша, ніж у лекції, — навмисно: якщо ти зможеш повторити ту саму
механіку на своїх даних, значить, справа не в завченому прикладі. Працюємо з
бібліотечним каталогом: пошук книжки за шифром, перевірка читацького квитка,
статистика звернень. Усе роби в окремому файлі katalog.py або в новому зошиті —
практику не редагуй, вона знадобиться для звірки.
Спільна заготовка для всіх трьох рівнів:
import time
КАТАЛОГ = {
"А-101": "Кобзар",
"Б-204": "Місто",
"В-307": "Тигролови",
"Г-412": "Записки українського самашедшого",
"Д-518": "Інтернат",
}
def знайти_книгу(шифр):
"""Умовно повільний пошук у каталозі: 30 мс на звернення."""
time.sleep(0.03)
return КАТАЛОГ[шифр]
рівень 1Рівень 1 — База
Напиши декоратор @журнал, який записує кожен виклик функції у спільний список, і
почепи його на знайти_книгу.
- Обгортка має приймати
*args, **kwargsі повертати результат незміненим. - У список
ЖУРНАЛклади кортеж(імʼя_функції, аргументи, результат). - Обовʼязково постав
@wraps(func). - Зроби дві версії декоратора — з
@wrapsі без нього — і застосуй їх до двох однакових функцій, щоб порівняти службові поля.
Зроблено, якщо:
- проходить
assert знайти_книгу("А-101") == "Кобзар"— результат не змінився; - проходить
assert len(ЖУРНАЛ) == 3після трьох викликів, і в записах видно правильні шифри; - проходить
assert знайти_книгу.__name__ == "знайти_книгу"для версії зwraps; - проходить
assert без_wraps.__name__ == "обгортка"для версії без нього — і в коментарі поруч написано одним реченням, чому це шкодить.
рівень 2Рівень 2 — Плюс
Напиши декоратор з аргументом @не_довше(секунд=0.05), який міряє час роботи функції
і, якщо вона працювала довше за дозволене, друкує попередження. Результат при цьому
має повертатись завжди — декоратор не має права ковтати відповідь.
- Три рівні вкладеності:
не_довше(секунд)→декоратор(func)→обгортка(*args, **kwargs). - Перевір його на двох функціях: швидкій (без
sleep) і повільній (знайти_книгузіsleep(0.03)), а також на межі — зроби функцію зіsleep(0.08). - Дай декоратору другий параметр
тихо=False: якщоTrue, попередження не друкується, а лише додається у списокПЕРЕВИЩЕННЯ. - Запиши в коментарі розгортку цього декоратора без синтаксичного цукру — рядком.
Зроблено, якщо:
- проходить
assert швидка(2) == 4іassert повільна("А-101") == "Кобзар"— обидві функції повертають те, що мають; - три
assert-и перевіряють, що попередження зʼявилось саме для повільної функції і не зʼявилось для швидкої; - проходить
assert len(ПЕРЕВИЩЕННЯ) == 1для версії зтихо=True; - у коментарі є рядок виду
повільна = не_довше(секунд=0.05)(повільна); - проходить
assert повільна.__name__ == "повільна"—@wrapsна місці.
рівень 3Рівень 3 — Виклик
Напиши власний LRU-кеш і доведи, що він поводиться точно так, як бібліотечний.
- Декоратор
@мій_lru(maxsize=N): словникаргументи → результатплюс список порядку звернень. При переповненні викидай найдавніше вживаний запис. - Додай метод
статистика(), що повертає кортеж(влучань, промахів, розмір)— якcache_info()у бібліотеки. - Візьми послідовність із щонайменше 40 запитів до пʼяти шифрів (склади її сам,
з повторами) і прожени її двічі: через свій декоратор і через
functools.lru_cache(maxsize=N). - Порівняй
hitsіmissesдляmaxsize= 1, 2, 3, 5 — усі чотири випадки. - Зміряй час обох варіантів і побудуй табличку «maxsize · влучань · промахів · час».
- Додай один випадок, де твій кеш і бібліотечний розійшлися б, якби ти реалізував витіснення неправильно (наприклад, викидав найстаріший доданий, а не найдавніше вживаний), і покажи це числами.
Зроблено, якщо:
- проходить
assert мої_відповіді == бібліотечні_відповіді— значення однакові; - для всіх чотирьох
maxsizeпроходитьassert моя_статистика[:2] == (info.hits, info.misses); - у табличці видно, що при
maxsize=5промахів рівно стільки, скільки різних шифрів, і що менше вже не буває; - пункт 6 підкріплений числами: наведено послідовність, на якій FIFO і LRU дають різну кількість промахів, і різниця показана в коді;
- проходить
assert знайти_книгу.__name__ == "знайти_книгу"— свій декоратор теж користується@wraps.
Підказки
- Рівень 1. Якщо
ЖУРНАЛвиявився порожнім — перевір, чи не забув ти повернути обгортку з декоратора:return обгорткабез дужок. З дужками Python спробує викликати її на місці й одразу впаде. - Рівень 2. Три рівні легше писати згори вниз, а читати — знизу вгору. Спочатку напиши звичайний декоратор без параметрів, переконайся, що він працює, і лише потім обгорни його ще однією функцією, яка приймає налаштування.
- Рівень 2, друга пастка.
@не_довшебез дужок не дасть помилки в цьому рядку — функція просто поїде в параметрсекунд. Зламається все пізніше й в іншому місці. - Рівень 3. LRU відрізняється від FIFO одним рухом: при влучанні запис теж переїжджає в кінець списку порядку. Забудеш це — отримаєш чергу, а не LRU.
- Рівень 3, вимірювання. Не порівнюй час одного виклику:
sleepдає похибку. Міряй увесь прогін послідовностіtime.perf_counter(), як у практиці. - Перевіряти зручно на коротких шифрах і
sleep(0.01)— механізм той самий, а чекати втричі менше.