Менеджеры контекста
Менеджеры контекста позволяют выделять и освобождать ресурсы строго по необходимости. Самый популярный пример использования менеджера контекста - выражение
with
. Предположим, у вас есть две связанные операции, которые вы хотите исполнить в паре, поместив между ними блок кода. Менеджеры контекста позволяют сделать именно это. Например:with open('some_file', 'w') as opened_file:
opened_file.write('Hola!')
Код выше открывает файл, записывает в него данные и закрывает файл после этого. При возникновении ошибки при записи данных в файл менеджер контекста попробует его закрыт ь. Этот код эквивалентен следующему:
file = open('some_file', 'w')
try:
file.write('Hola!')
finally:
file.close()
Сравнив с первым блоком кода, мы можем заметить замену шаблонного кода на
with
. Основное преимущество использования with
- это гарантия закрытия файла вне зависимости от того, как будет завершён вложенный код.Распространенный паттерн использования контекстных менеджеров - блокирование и разблокирование ресурсов, а также закрытие открытых файлов (как я уже показал выше).
Давайте посмотрим, как мы можем написать свой собственный менеджер контекста. Это позволит нам лучше понять логику его работы.
Необходимый минимум функциональности контекстного менеджера требует методов
__enter__
и __exit__
. Давайте напишем свой контекстный менеджер для работы с файлами и изучим основы. class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, traceback):
self.file_obj.close()
Просто определив методы
__enter__
и __exit__
, мы можем использовать новый контекстный менеджер с with
. Давайте попробуем:with File('demo.txt', 'w') as opened_file:
opened_file.write('Hola!')
Метод
__exit__
принимает три аргумента. Они обязательны для любого метода __exit__
класса контекстного менеджера. Давайте обсудим логику работы:- 1.
with
сохраняет метод__exit__
классаFile
. - 2.Следует вызов метода
__enter__
классаFile
. - 3.Метод
__enter__
открывает файл и возвращает его. - 4.Дескриптор файла передается в
opened_file
. - 5.Мы записываем информацию в файл при помощи
.write()
. - 6.
with
вызывает сохраненный__exit__
метод. - 7.Метод
__exit__
закрывает файл.
Мы ещё не успели поговорить об аргументах
type
, value
и traceback
метода __exit__
. Между четвертым и шестым шагом при возникновении исключения, Python передает тип, значение и обратную трассировку исключения методу __exit__
. Это позволяет методу __exit__
выбирать способ закрытия файла и выполнять дополнительные действия при необходимости. В нашем случае, мы не уделяем им особого внимания.Что если объект файла вызвал исключение? Возможно, мы пытаемся вызывать метод на объекте, который его не поддерживает. Например:
with File('demo.txt', 'w') as opened_file:
opened_file.undefined_function('Hola!')
Давайте разберём шаги, которые выполняет
with
при возникновении исключения:- 1.Тип, значение и обратная трассировка ошибки передается в метод
__exit__
. - 2.Обработка исключения передается методу
__exit__
- 3.Если
__exit__
возвращаетTrue
, то исключение было корректно обработано. - 4.При возврате любого другого значения
with
вызывает исключение.
В нашем случае метод
__exit__
возвращает None
(при отсутствии выражения return
метод в Python возвращает None
). Таким образом, with
вызывает исключение:Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: 'file' object has no attribute 'undefined_function'
Давайте попробуем обработать исключение в методе
__exit__
:class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, traceback):
print("Исключение было обработано")
self.file_obj.close()
return True
with File('demo.txt', 'w') as opened_file:
opened_file.undefined_function()
# Вывод: Исключение было обработано
Наш метод
__exit__
возвращает True
, таким образом with
не вызывает исключение.Это не единственный способ реализации контекстных менеджеров - есть и другой и мы посмотрим на него в следующем параграфе.
Мы также можем реализовать менеджер контекста через декораторы и генераторы. В Python присутствует модуль
contextlib
специально для этой цели. Вместо написания класса, мы можем реализовать менеджер контекста из функции-генератора. Посмотрим на простой пример:from contextlib import contextmanager
@contextmanager
def open_file(name):
f = open(name, 'w')
yield f
f.close()
Отлично! Реализация менеджера контекста таким способом смотрится более интуитивной и простой. Тем не менее, этот ме тод требует определённых знаний о генераторах,
yield
и декораторах. В примере выше мы не обрабатываем возможные исключения. В целом, он почти такой же, что и предыдущий.Давайте чуть подробнее разберем этот подход:
- 1.Python встречает ключевое слово
yield
. Благодаря этому он создаетгенератор, а не простую функцию. - 2.Благодаря декоратору,
contextmanager
вызывается с функциейopen_file
в качестве аргумента. - 3.Функция
contextmanager
возвращает генератор, обёрнутый в объектGeneratorContextManager
. - 4.
GeneratorContextManager
присваивается функцииopen_file
. Такимобразом, когда мы вызовем функциюopen_file
в следующий раз, тофактически обратимся к объектуGeneratorContextManager
.
Теперь, когда мы знаем всё это, мы можем использовать созданный менеджер контекста следующим образом:
with open_file('some_file') as f:
f.write('hola!')
Last modified 5yr ago