Контекстные менеджеры
Список вопросов к 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
| |
Тайминг выполнения кода
| |