Контекстные менеджеры
Список вопросов к Python собеседованию
- Контекстные менеджеры нужны там, где есть “настройка” -> блок кода -> “уборка”, при этом “настройку” и “уборку” нужно выполнить в паре.
- У контекстного менеджера обязательно присутствуют атрибуты
__enter__
и__exit__
. Их добавление обеспечивает реализацию протокола контекстного менеджера. - Не обязательно писать целый класс для нового контекстного менеджера, достаточно обернуть генератор в декоратор
contextmanager
из модуляcontextlib
. yield
стоит оборачивать в блокtry...finally
.- Контекстный менеджер определен в
PEP 343
. - Контекстные менеджеры предназначены для использования в качестве более сжатого механизма управления ресурсами, чем
try...finally
- Контекстные менеджеры дают нам надежный метод очистки ресурсов (т.к. вызов метода деструктора Python
del
не всегда гарантируется).
Контекстные менеджеры
Контекстные менеджеры позволяют выделять и освобождать ресурсы именно тогда, когда нам это нужно. Наиболее широко используемым примером контекстных менеджеров является оператор with
.
Допустим, у нас есть две связанные операции, которые необходимо выполнить в паре + блок кода между ними. Инструкция with
создает контекст выполнения, который позволяет запускать группу операторов под управлением контекстного менеджера.
По сравнению с традиционными конструкциями try ... finally
, инструкция with
делает код более понятным, безопасным и многоразовым. Многие классы в стандартной библиотеке поддерживают оператор with
. Классическим примером этого является open()
, который позволяет работать с файловыми объектами используя with
:
|
|
В общем случае синтаксис использования with
выглядит следующим образом:
|
|
expression
, идущее после with
должно возвращать объект, реализующий протокол управления контекстом. Этот протокол состоит из двух специальных методов:
__enter__
вызывается операторомwith
для входа в контекст выполнения;__exit__
вызывается, когда выполнение покидает блок кодаwith
.
Спецификатор as
необязателен. Если мы используем target_var
с as
, то значение возвращенное методом __enter__
для объекта контекстного менеджера привязывается к этой переменной.
None
из __enter__
, потому что у них нет объекта, который можно было бы вернуть вызывяющей стороне. В этих случаях использование target_var
не имеет смысла.Последовательность работы:
- Вызов
expression
для получения контекстного менеджера. - Сохранение методов контекстного менеджера _
_enter__
и__exit__
для последующего использования. - Вызов метода
__enter__
и сохранение возвращаемого значения вtarget_var
(еслиtarget_var
используется). - Выполнение блока кода внутри
with
. - Вызов метода
__exit__
в контекстном менеджере после завершения блока кода внутриwith
.
Наиболее частые сценарии использования:
- Open–Close - например, файла, или сокета
- Lock–Release - работа с данными в многопоточном приложении
- Start–Stop - например, для запуска таймера и его автоматической остановки.
- Change–Reset - например, приложение должно подключиться к нескольким источникам данных и у него есть соединение по умолчанию.
- Create-Delete
- Enter-Exit
- Setup-Teardown
Контекстный менеджер из класса
Необходимый минимум функциональности контекстного менеджера требует методов __enter__
и __exit__
.
- Метод
__enter__(self)
выполняется до входа в блок. Методу можно возвратить текущий экземпляр класса, что бы к нему можно было обращаться через инструкциюas
. - Метод
__exit__(self, ex_type, ex_val, ex_trace)
выполняется после выхода из блокаwith
, и содержит три параметра —ex_type
,ex_value
иex_tr
. Переменнаяex_type
содержит в себе класс исключения, которое было возбуждено,ex_value
— сообщение исключения.
Чтобы превратить класс в контекстный менеджер нужно определить в нем два этих метода:
|
|
Шаги, которые выполняет with
при возникновении исключения:
- Тип, значение и обратная трассировка ошибки передается в метод
__exit__
. - Обработка исключения передается методу
__exit__
- Если
__exit__
возвращаетTrue
, то исключение было корректно обработано. - При возврате любого другого значения
with
вызывает исключение.
Контекстный менеджер из генератора
Мы также можем реализовать менеджер контекста через декораторы и генераторы. В Python присутствует модуль contextlib
специально для этой цели. Вместо написания класса, мы можем реализовать менеджер контекста из функции-генератора.
|
|
Пошаговый разбор данного подхода:
- Python встречает ключевое слово
yield
. Благодаря этому он создает генератор, а не простую функцию. - Благодаря декоратору,
contextmanager
вызывается с функциейopen_file
в качестве аргумента. - Функция
contextmanager
возвращает генератор, обёрнутый в объектGeneratorContextManager
. GeneratorContextManager
присваивается функцииopen_file
. Таким образом, когда мы вызовем функциюopen_file
в следующий раз, то фактически обратимся к объектуGeneratorContextManager
.
Контекстный менеджер как декоратор
Можно использовать контекстные менеджеры в качестве декораторов. Для этого при определении класса необходимо наследоваться от класса contextlib.ContextDecorator
.
|
|
Результат:
|
|
Вложенные контекстные менеджеры
Инструкция with
поддерживает несколько вложенных контекстных менеджеров. Можно использовать любое количество контекстных менеджеров, разделенных запятыми:
|
|
Асинхронные контекстные менеджеры
Распространенной практикой при написании асинхронных контекстных менеджеров является внедрение четырех специальных методов:
__aenter__
__aexit__
__enter__
__exit__
|
|
Примеры менеджеров контекста
Создание сессии в SQLAlchemy
|
|
Тайминг выполнения кода
|
|