Менеджеры контекста
Менеджеры контекста позволяют выделять и освобождать ресурсы строго по необходимости. Самый популярный пример использования менеджера контекста - выражение with
. Предположим, у вас есть две связанные операции, которые вы хотите исполнить в паре, поместив между ними блок кода. Менеджеры контекста позволяют сделать именно это. Например:
Код выше открывает файл, записывает в него данные и закрывает файл после этого. При возникновении ошибки при записи данных в файл менеджер контекста попробует его закрыть. Этот код эквивалентен следующему:
Сравнив с первым блоком кода, мы можем заметить замену шаблонного кода на with
. Основное преимущество использования with
- это гарантия закрытия файла вне зависимости от того, как будет завершён вложенный код.
Распространенный паттерн использования контекстных менеджеров - блокирование и разблокирование ресурсов, а также закрытие открытых файлов (как я уже показал выше).
Давайте посмотрим, как мы можем написать свой собственный менеджер контекста. Это позволит нам лучше понять логику его работы.
Контекст-менеджер как класс
Необходимый минимум функциональности контекстного менеджера требует методов __enter__
и __exit__
. Давайте напишем свой контекстный менеджер для работы с файлами и изучим основы.
Просто определив методы __enter__
и __exit__
, мы можем использовать новый контекстный менеджер с with
. Давайте попробуем:
Метод __exit__
принимает три аргумента. Они обязательны для любого метода __exit__
класса контекстного менеджера. Давайте обсудим логику работы:
with
сохраняет метод__exit__
классаFile
.Следует вызов метода
__enter__
классаFile
.Метод
__enter__
открывает файл и возвращает его.Дескриптор файла передается в
opened_file
.Мы записываем информацию в файл при помощи
.write()
.with
вызывает сохраненный__exit__
метод.Метод
__exit__
закрывает файл.
Обработка исключений
Мы ещё не успели поговорить об аргументах type
, value
и traceback
метода __exit__
. Между четвертым и шестым шагом при возникновении исключения, Python передает тип, значение и обратную трассировку исключения методу __exit__
. Это позволяет методу __exit__
выбирать способ закрытия файла и выполнять дополнительные действия при необходимости. В нашем случае, мы не уделяем им особого внимания.
Что если объект файла вызвал исключение? Возможно, мы пытаемся вызывать метод на объекте, который его не поддерживает. Например:
Давайте разберём шаги, которые выполняет with
при возникновении исключения:
Тип, значение и обратная трассировка ошибки передается в метод
__exit__
.Обработка исключения передается методу
__exit__
Если
__exit__
возвращаетTrue
, то исключение было корректно обработано.При возврате любого другого значения
with
вызывает исключение.
В нашем случае метод __exit__
возвращает None
(при отсутствии выражения return
метод в Python возвращает None
). Таким образом, with
вызывает исключение:
Давайте попробуем обработать исключение в методе __exit__
:
Наш метод __exit__
возвращает True
, таким образом with
не вызывает исключение.
Это не единственный способ реализации контекстных менеджеров - есть и другой и мы посмотрим на него в следующем параграфе.
Контекст-менеджер из генератора
Мы также можем реализовать менеджер контекста через декораторы и генераторы. В Python присутствует модуль contextlib
специально для этой цели. Вместо написания класса, мы можем реализовать менеджер контекста из функции-генератора. Посмотрим на простой пример:
Отлично! Реализация менеджера контекста таким способом смотрится более интуитивной и простой. Тем не менее, этот метод требует определённых знаний о генераторах, yield
и декораторах. В примере выше мы не обрабатываем возможные исключения. В целом, он почти такой же, что и предыдущий.
Давайте чуть подробнее разберем этот подход:
Python встречает ключевое слово
yield
. Благодаря этому он создаетгенератор, а не простую функцию.
Благодаря декоратору,
contextmanager
вызывается с функциейopen_file
в качестве аргумента.Функция
contextmanager
возвращает генератор, обёрнутый в объектGeneratorContextManager
.GeneratorContextManager
присваивается функцииopen_file
. Такимобразом, когда мы вызовем функцию
open_file
в следующий раз, тофактически обратимся к объекту
GeneratorContextManager
.
Теперь, когда мы знаем всё это, мы можем использовать созданный менеджер контекста следующим образом:
Last updated