Українська
Параметризовані декоратори
Параметризовані декоратори, повтори й час
@retry(times=3) спершу викликає фабрику декораторів, потім отриманий декоратор обгортає функцію. Потрібні три рівні: параметри політики, оригінальна функція, аргументи виклику. Не повторюйте будь-який Exception: помилка програміста або недопустимий аргумент не стає правильним після наступної спроби.
Нижче повторюються лише TimeoutError; випадковість не потрібна. Перші два виклики контрольовано неуспішні. Таймер охоплює весь набір спроб, бо він зовнішній. Час виміряно perf_counter, але для відтворюваного виводу друкується лише його невід’ємність. Це не результат вимірювання швидкодії алгоритму.
py
from collections.abc import Callable
from functools import wraps
from time import perf_counter
from typing import ParamSpec, TypeVar
P = ParamSpec("P")
R = TypeVar("R")
def retry(times: int) -> Callable[[Callable[P, R]], Callable[P, R]]:
if times < 1:
raise ValueError("Потрібна хоча б одна спроба")
def decorate(func: Callable[P, R]) -> Callable[P, R]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except TimeoutError:
print("Невдала спроба:", attempt)
if attempt == times:
raise
raise AssertionError("Недосяжна гілка")
return wrapper
return decorate
def timer(func: Callable[P, R]) -> Callable[P, R]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
started = perf_counter()
try:
return func(*args, **kwargs)
finally:
elapsed = perf_counter() - started
print(func.__name__, "час >= 0:", elapsed >= 0)
return wrapper
attempts = 0
@timer
@retry(times=3)
def fetch() -> str:
global attempts
attempts += 1
if attempts < 3:
raise TimeoutError("Навчальна відмова")
return "готово"
print(fetch())Невдала спроба: 1
Невдала спроба: 2
fetch час >= 0: True
готовоГлобальний лічильник тут – контрольований імітатор джерела. У прикладному коді стан краще передати явно або сховати у фабриці. Якщо поміняти декоратори місцями, час друкуватиметься для кожної спроби, включно з невдалими. Повтор операції безпечний лише тоді, коли він не дублює незворотну дію. Навчальний приклад не робить мережевих запитів і не надсилає платежів.
Знімок екрана
Run fetch example. Show two failures, timer check, ready result; no invented timings.
Рис. 7.6. Контрольовані повтори й повідомлення таймера
Кешування результатів
cache не обмежує число збережених результатів; lru_cache із maxsize видаляє найдавніше не використані записи після переповнення. Аргументи мають бути хешованими. Кеш корисний для чистого обчислення, але приховає потрібний побічний ефект, якщо застосувати його до функції друку чи зміни зовнішнього стану. https://docs.python.org/3.14/library/functools.html.
py
from functools import lru_cache
@lru_cache(maxsize=4)
def square(value: int) -> int:
return value * value
print(square(5), square(5), square(6))
info = square.cache_info()
print(info.hits, info.misses, info.currsize)
square.cache_clear()
print(square.cache_info().currsize)25 25 36
1 2 2
0Кеш не має автоматичного строку придатності. Для даних, які змінюються, потрібне явне очищення, ключ версії або окрема політика часу життя. Не кешуйте генераторний об’єкт: повторний виклик може отримати той самий уже вичерпаний генератор. Якщо обсяг малий, можна кешувати незмінний кортеж результатів і щоразу створювати новий ітератор поверх нього.
Налагодження та типові помилки
Зупиніть генератор на рядку yield і спостерігайте локальні значення. Наступний запит споживача продовжує виконання після yield, а не з початку функції. Окремо перевіряйте створення генератора й перший запит: виняток перевірки входу може виникнути саме на першому next. У PyCharm дивіться кадр джерела та кадр споживача, а не лише останнє надруковане число.
Не змінюйте список під час його обходу без чітко продуманого алгоритму. Для повторного читання не повертайте один глобальний ітератор. Не зберігайте об’єкти груп groupby для пізнішого обходу без матеріалізації. Не називайте сортування потоковою операцією зі сталою пам’яттю. У декораторі перевіряйте __name__, повернення значення, передавання іменованих аргументів і поширення винятку.
Для нескінченного джерела тест має містити межу споживання. Для пакетів перевірте нуль елементів, точну кратність і короткий хвіст. Для груп – повторення ключа через інший ключ. Для кешу – перший і повторний виклики після очищення. Такий набір доводить конкретну поведінку, тоді як один успішний великий запуск може приховувати більшість помилок.