← все задачи
Python · задача 7 из 10
Контекстный менеджер для замера времени
Средний
15 минут
контекстные менеджерыcontextlibисключения
Условие
Напишите контекстный менеджер timer, который измеряет время выполнения блока и логирует его. Время должно измеряться и тогда, когда внутри блока произошло исключение, а само исключение должно уйти наверх.
Что требуется
- Работает как with timer("загрузка"):
- При исключении время всё равно записывается, исключение не подавляется
- Замеренное время доступно снаружи блока
Пример
with timer("запрос в БД") as t:
rows = query()
print(t.elapsed) # 0.42
# лог: запрос в БД: 0.420с
Сначала уточните
Вопросы до кода — половина оценки. Молча начать печатать хуже, чем задать два вопроса.
- Логировать или возвращать значение — куда должен уходить результат?
- Нужен ли вариант как декоратор для функции целиком?
- Какая точность нужна и годится ли monotonic-часы?
Показать решение Скрыть решение
Решение
import logging
import time
from contextlib import contextmanager
logger = logging.getLogger(__name__)
class timer:
def __init__(self, label):
self.label = label
self.elapsed = None
def __enter__(self):
self.started = time.perf_counter()
return self # чтобы работало "as t"
def __exit__(self, exc_type, exc, tb):
self.elapsed = time.perf_counter() - self.started
logger.info("%s: %.3fс%s", self.label, self.elapsed,
" (с ошибкой)" if exc_type else "")
return False # False — исключение летит дальше
# Короткий вариант, если объект наружу не нужен:
@contextmanager
def timer_cm(label):
started = time.perf_counter()
try:
yield
finally:
logger.info("%s: %.3fс", label, time.perf_counter() - started)
Почему так
Почему __exit__ возвращает False
- Истинное значение из __exit__ подавляет исключение — блок «проглотит» ошибку, и это молчаливая потеря данных
- return False (или просто ничего) означает «я прибрался, исключение пусть летит дальше»
- Это ровно тот вопрос, ради которого задачу и дают: разницу между «прибраться» и «проглотить» понимают не все
Почему perf_counter, а не time.time
- time.time берёт системные часы: перевод времени или синхронизация NTP могут дать отрицательный интервал
- perf_counter — монотонные часы с максимальным разрешением, они ровно для замеров и созданы
- Для измерения процессорного времени вместо реального есть process_time — уточнение, которое любят
Почему try/finally в варианте с contextmanager
- Без finally при исключении внутри блока код после yield не выполнится и замер потеряется
- finally гарантирует, что время запишется в любом случае, а исключение продолжит путь наверх
- Класс против @contextmanager — вопрос вкуса, но объект нужен, когда наружу отдаётся состояние (elapsed)
Что спросят дальше
- Спросят, как сделать из этого ещё и декоратор: наследование от ContextDecorator даёт это бесплатно
- Спросят про async with — это __aenter__/__aexit__ или @asynccontextmanager
- Могут попросить складывать метрику в Prometheus или statsd — покажите, что знаете, куда такие замеры уходят в проде
Следующая задача
LRU-кэш своими руками — Проверяют структуры данных и понимание, почему обычный словарь тут не спасает.