← Назад к списку
ПрограммированиеPythonMiddle

Реализуйте контекстный менеджер Timer двумя способами: классом и через contextlib.

Короткий ответ

  • Классовый вариант: __enter__ запускает отсчёт, __exit__ печатает
  • __enter__ возвращает self, чтобы читать результат через as
  • __exit__ не возвращает True — исключения пробрасываются
  • Вариант contextlib: генератор с yield внутри try/finally
  • time.perf_counter() точнее time.time() для замеров
  • Замер должен сработать и при исключении в блоке

Оба варианта фиксируют время в точке входа и считают длительность при любом выходе из блока; классовый даёт объект с результатом, генераторный короче.

Как сказать вслух

пример ответа

В классовом варианте я запускаю отсчёт в enter и возвращаю self, чтобы через as можно было прочитать длительность, а в exit считаю разницу — он вызовется даже при исключении, и подавлять его я не буду. Во втором варианте пишу функцию-генератор с одним yield под декоратором contextmanager, а замер кладу в try/finally. Для времени беру perf_counter — он монотонный и не прыгает при переводе системных часов.

Подробный ответ

Основной ответ

Классовая реализация: __enter__ сохраняет time.perf_counter() и возвращает self — тогда with Timer() as t даёт объект, у которого после блока доступен t.elapsed. __exit__(exc_type, exc, tb) вычисляет разницу и возвращает None (ложь), чтобы исключения из блока шли наружу; сам факт его вызова гарантирован при любом выходе. Генераторная версия через @contextmanager компактнее: код до yield — вход, finally после — выход; yield может отдать изменяемый объект (например, словарь), в который finally допишет результат. perf_counter выбирают потому, что это монотонные часы высокого разрешения, не зависящие от перевода системного времени. Задача демонстрирует эквивалентность двух стилей и понимание семантики __exit__.

Ключевые моменты

  • Возврат self из __enter__. Иначе as-переменная будет None и результат замера некуда положить.
  • Семантика __exit__. Ложный возврат пробрасывает исключение; замер при этом всё равно фиксируется.
  • try/finally вокруг yield. В генераторной версии без finally исключение в блоке отменит код «после выхода».
  • perf_counter. Монотонный таймер для интервалов; time.time() может скакнуть из-за NTP-синхронизации.

Практический контекст

Такой таймер — реальный утилитарный код: замер запросов к БД, внешних вызовов, этапов пайплайна перед передачей в метрики. На собеседовании задача проверяет знание протокола контекстных менеджеров и аккуратность с исключениями; сильный кандидат сам проговаривает, почему __exit__ не должен возвращать True и чем perf_counter лучше time.

Пример кода

import time
from contextlib import contextmanager

class Timer:
    def __enter__(self):
        self._start = time.perf_counter()
        return self

    def __exit__(self, exc_type, exc, tb):
        self.elapsed = time.perf_counter() - self._start
        return None  # исключения не подавляем

@contextmanager
def timer():
    start = time.perf_counter()
    result = {}
    try:
        yield result
    finally:
        result["elapsed"] = time.perf_counter() - start

with Timer() as t:
    sum(range(10**6))
print(t.elapsed)

Частые ошибки

  • Не возвращают self из __enter__, и переменная после as оказывается None
  • Возвращают True из __exit__ и молча проглатывают исключения блока
  • В генераторной версии пишут код после yield без finally — при ошибке замер теряется

ИП Кочкин Алексей Сергеевич · ИНН 390509026279 · ОГРНИП 325390000030973 · jiniys2005@yandex.ru