Українська
Менеджери контексту
Менеджери контексту
Конструкція with потрібна для дій, які мають завершуватися парно: відкрити й закрити, почати й закінчити вимірювання. __enter__ виконує вхід і повертає значення для as. __exit__ отримує тип винятку, його об’єкт і traceback або три None, якщо тіло завершилося нормально. Істинний результат __exit__ пригнічує виняток; False або None передають його далі. Випадкове return True може приховати збій програми.
Приклад 3. Таймер у контексті
Реальний час залежить від комп’ютера, тому друкуватимемо перевірку невід’ємності тривалості, а не вигадане стабільне число мілісекунд. perf_counter призначений для вимірювання інтервалів, а не для календарної дати. Винятки з тіла таймера не пригнічуються.
py
from time import perf_counter
from types import TracebackType
class Timer:
def __init__(self) -> None:
self.started = 0.0
self.elapsed = 0.0
def __enter__(self) -> "Timer":
self.started = perf_counter()
return self
def __exit__(self, kind: type[BaseException] | None,
error: BaseException | None,
trace: TracebackType | None) -> bool:
self.elapsed = perf_counter() - self.started
return False
with Timer() as timer:
total = sum(range(1000))
print(total, timer.elapsed >= 0)499500 TrueЦей таймер не призначений для вкладеного повторного входу в один екземпляр: другий вхід перезапише початок. Для вкладених вимірів створюйте окремі об’єкти. Якщо помилка виникла в __enter__, __exit__ для цього входу не викликається, тому частково отриманий ресурс потрібно очистити безпосередньо під час входу.
Альтернатива – @contextmanager із contextlib: код до yield виконує підготовку, код у finally після yield – завершення. Генератор має віддати значення рівно один раз. Виняток із тіла потрапляє в точку yield; якщо перехопити його й не підняти знову, він буде пригнічений. Довідка: https://docs.python.org/3.14/library/contextlib.html#contextlib.contextmanager.
Перевірка завершення при винятку
Короткий контекст можна описати генераторною функцією. У наступній програмі журнал подій є звичайним списком: він дозволяє точно перевірити порядок підготовки, роботи й завершення без залежності від часу або файлової системи. Завершальна подія додається в finally, тому зберігається і після помилки в тілі with.
py
from collections.abc import Iterator
from contextlib import contextmanager
@contextmanager
def tracked(events: list[str]) -> Iterator[None]:
events.append("вхід")
try:
yield
finally:
events.append("вихід")
events: list[str] = []
try:
with tracked(events):
events.append("робота")
raise ValueError("навмисна відмова")
except ValueError:
events.append("обробка")
print(" -> ".join(events))вхід -> робота -> вихід -> обробкаПорядок доводить дві властивості: завершення контексту відбулося до зовнішнього обробника, а виняток не зник. Якби генератор перехопив ValueError біля yield без повторного raise, події «обробка» не було б. Таку поведінку можна дозволяти лише для конкретного задокументованого винятку, як у спеціалізованому контексті пригнічення необов’язкової помилки.
Одна фабрика tracked може створювати багато окремих контекстів, але вже використаний генераторний об’єкт контексту не слід повторно відкривати. У кожному with викликайте tracked(events) заново. Це відокремлює життєвий цикл одного входу від налаштування фабрики. Для реального ресурсу на місці додавання подій були б операції отримання й звільнення. Тест на помилковий шлях необхідний: успішний запуск сам по собі не перевіряє гарантію очищення.