Какую роль играет метод _recreate_cm в ContextDecorator при повторных вызовах декорированной функции?
_recreate_cm возвращает контекстный менеджер, который будет использован для конкретного вызова декорированной функции. Для одноразовых или содержащих состояние менеджеров этот метод должен создавать новый экземпляр, чтобы вызовы не делили внутреннее состояние и не пытались повторно войти в уже использованный контекст.
ContextDecorator объединяет два способа применения одной логики: явное управление ресурсом через with и автоматическое оборачивание функции декоратором. Такой подход устраняет дублирование, когда одинаковые действия входа и выхода нужны и вокруг блока кода, и вокруг всей функции.
Однако жизненный цикл контекстного менеджера в with и жизненный цикл декоратора различаются. Декорированная функция может вызываться многократно, поэтому одно и то же состояние контекста не всегда можно безопасно переиспользовать.
Если декоратор при каждом вызове использует один и тот же экземпляр контекстного менеджера, его состояние может протечь между вызовами. Для генераторного контекстного менеджера это особенно важно: один экземпляр связан с конкретным генератором и не предназначен для повторного входа после завершения.
Неверная реализация может привести к ошибкам повторного использования, некорректному откату транзакции, смешиванию данных вложенных вызовов или освобождению ресурса не в том жизненном цикле, в котором он был получен.
Внутренний вызов декоратора на базе ContextDecorator логически выполняется так: перед запуском функции он получает контекст через self._recreate_cm(), входит в него, вызывает функцию и затем гарантированно выходит из контекста.
У базового ContextDecorator реализация _recreate_cm возвращает self. Это приемлемо только для контекстного менеджера, который действительно безопасно переиспользовать и который корректно поддерживает вложенные или повторные входы.
Декоратор, созданный contextlib.contextmanager, переопределяет этот механизм: _recreate_cm создаёт новый экземпляр генераторного контекстного менеджера для каждого вызова декорированной функции. Поэтому функция получает отдельный генератор и отдельный жизненный цикл контекста.
При каждом вызове work создаётся новый контекст: сначала печатается begin, затем выполняется функция, после чего печатается end. Один генератор не используется повторно.
При создании собственного класса на основе ContextDecorator нужно решить, допустимо ли возвращать тот же объект. Если объект хранит текущий ресурс, флаг входа, идентификатор операции или данные исключения, обычно требуется переопределить _recreate_cm и возвращать новый экземпляр с необходимой конфигурацией.
Это не означает, что копирование объекта всегда безопасно: новый экземпляр должен получать независимое состояние, но может ссылаться на общий потокобезопасный сервис или фабрику ресурсов. Для асинхронного кода следует использовать асинхронные контекстные менеджеры и AsyncContextDecorator, а не переносить синхронную реализацию без адаптации.
Команда добавляет декоратор для измерения длительности операции. Контекстный менеджер хранит время начала в поле экземпляра и должен записывать результат после выхода из контекста. Функция вызывается параллельно из нескольких потоков.
Первый вариант — один общий экземпляр с базовой реализацией ContextDecorator. Он прост, но поле времени начала будет перезаписываться конкурентными вызовами; измерения станут недостоверными. Добавление блокировки может защитить запись, но не устраняет смешивание логических операций без более сложного состояния.
Второй вариант — _recreate_cm создаёт отдельный экземпляр менеджера для каждого вызова. Это изолирует время начала и данные завершения, а общий компонент записи метрик остаётся потокобезопасным. Такой вариант выбранным решением делает жизненный цикл измерения локальным вызову функции; результатом становятся корректные метрики без необходимости синхронизировать состояние самого контекста.
_recreate_cm обязан возвращать новый объект?Нет. Базовый ContextDecorator возвращает тот же экземпляр, если его переиспользование безопасно. Новый объект необходим не по формальному правилу, а когда контекст содержит одноразовое или относящееся к конкретному входу состояние.
contextmanager подходит для декорирования многократно вызываемой функции?Вызов функции, помеченной contextmanager, возвращает объект генераторного контекстного менеджера. При использовании этого объекта как декоратора механизм ContextDecorator обращается к его _recreate_cm, который создаёт новый менеджер с новым генератором для каждого вызова функции. Поэтому декоратор не пытается повторно запустить завершённый генератор.
_recreate_cm, но забыть перенести конфигурацию?Новый менеджер будет изолированным, но может потерять настройки исходного: имя ресурса, уровень журналирования, фабрику или другие параметры. В результате проблема совместного состояния исчезнет, однако поведение декоратора станет отличаться от ожидаемого. Метод должен создавать новый экземпляр с независимым изменяемым состоянием и теми же неизменяемыми параметрами конфигурации.