Короткий ответ
Функция оборачивается декоратором @contextlib.contextmanager, внутри использует yield ровно один раз: код до yield работает как __enter__, значение после yield передаётся в as, а код после yield (в finally) работает как __exit__.
Как это работает подробнее
contextlib.contextmanager превращает функцию-генератор в фабрику контекстных менеджеров: первый next() доводит выполнение до yield и возвращает значение для as, а выход из блока with вызывает продолжение генератора после yield.
Пример кода
example-248.py
from contextlib import contextmanager
import time
@contextmanager
def timer(label):
start = time.perf_counter()
try:
yield
finally:
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed:.4f}s")
with timer("загрузка"):
time.sleep(0.1)
# строка с временем выведется гарантированно, даже если внутри блока было исключениеЧто использовать на практике
- Практика. Это удобный, менее многословный способ написать контекстный менеджер без отдельного класса — подходит для простых случаев: временного изменения состояния, замера времени, временной подмены конфигурации. Код освобождения ресурса нужно оборачивать в
try/finallyвокругyield.
Подводные камни
- Риски. Забывают
try/finallyвокругyield, из-за чего при исключении в блокеwithкод очистки не выполняется; делают больше одногоyieldв функции (RuntimeError) или забывают сам декоратор, из-за чего функция остаётся обычным генератором, несовместимым сwith.