В этой статье вы узнаете что такое контекстный менеджер, и как он упрощает работу в Python.
Что такое менеджер контекста?
Контекстный менеджер помогает упростить некоторые общие шаблоны управления ресурсами, абстрагируя их функциональность и позволяя их учитывать и повторно использовать. Давайте разберемся в упрощенном варианте.
Программисты время от времени работают с внешними ресурсами, такими как файлы, соединения с базами данных, блокировки и так далее. Контекстные менеджеры позволяют нам управлять этими ресурсами, указывая:
- Что делать, когда мы получаем доступ к ресурсу
- Что делать, когда доступ к ресурсу уже не нужен
Зачем нужен менеджер контекста?
Рассмотрим следующий пример:
for num in range(333):
a = open("temp.txt", "+a")
a.write(num)
a.close()
Обратите внимание, что я вызываю метод close(), чтобы гарантировать, что файловый дескриптор освобождается каждый раз. Если бы я этого не сделал, наша ОС (операционная система) в конечном итоге исчерпала бы свой разрешенный лимит на открытие файловых дескрипторов.
Однако я напишу более удобочитаемый вариант с помощью контекстного менеджера:
for num in range(444):
with open("temp.txt", "+a") as a:
a.write(num)
В этом примере open(“temp.txt”, “+a”) является менеджером контекста, который активируется с помощью оператора with. Обратите внимание, что мне не нужно было явно закрывать документ, контекстный менеджер позаботился об этом за меня. Точно так же в Python есть и другие предопределенные контекстные менеджеры, которые облегчают нашу работу.
Как создать менеджер контекста?
Существует два способа определения пользовательского контекстного менеджера:
- Определение на основе класса
- Определение на основе функций
Контекстный менеджер на основе классов
Давайте продолжим с нашим примером и попробуем определить наш собственный контекстный менеджер, который будет эмулировать функцию open():
class FileOpen:
def __init__(self, somefilename, filemode):
self.somefilename = somefilename
self.filemode = filemode
def __enter__(self):
print("run __enter__ method")
self.openfile = open(self.somefilename, self.filemode)
return self.openfile
def __exit__(self, *args):
print("run __exit__ method")
self.openfile.close()
for num in range(555):
with FileOpen("temp.txt", "+a") as a:
a.write(num)
- В методе _enter_ мы говорим что делать, когда я получаю доступ к ресурсам, т.е. получаем объект открытого файла.
- Метод _exit_ определяет, что делать, когда я выхожу из контекстного менеджера, то есть закрываю файл.
- Вы можете наблюдать за тем, как _enter_ и _exit_ запускаются при каждом цикле.
Обработка ошибок
Пример того как я обрабатываю FileNotFoundError:
for num in range(666):
try:
with FileOpen("temp.txt", "+a") as a:
a.write(num)
except FileNotFoundError:
print("Невозможно открыть файл")
Это базовый код обработки ошибок, который должен быть каждый раз, когда вы открываете файл. Давайте попробуем добавить его в наш созданный менеджер контекста:
class FileOpen:
def __init__(self, other_txt_file, filemode):
self.other_txt_file = other_txt_file
self.filemode = filemode
def __enter__(self):
print("run __enter__ method")
try:
self.opened_file = open(self.other_txt_file, self.filemode)
except FileNotFoundError:
print("Невозможно открыть файл")
return self.opened_file
def __exit__(self, exc_type, exc_value, exc_traceback):
print("run __exit__ method")
if exc_type is None:
self.opened_file.close()
return True
else:
return True
Изменения в атрибутах метода _exit_:
- exc_type – это тип класса ошибок, который вы получите при обработке ошибок в _enter_ (в данном случае AttributeError).
- exc_value – это значение ошибки, которое вы получите при обработке ошибок в _enter_.
- exc_traceback – это трассировка ошибки, которую вы получите при обработке ошибок в _enter_.
- Я возвращаю True, чтобы подавить трассировку ошибок (не путать с параметром exc_traceback).
Контекстные менеджеры на основе функций
Управление контекстом на основе функций осуществляется с помощью библиотеки под названием contextlib, с помощью которого мы можем превратить простую функцию-генератор в контекстный менеджер. Вот как выглядит типичный код:
from contextlib import contextmanager
@contextmanager
def test_func():
print("run method __enter__")
yield {}
print("run method __exit__")
with test_func() as f:
print(f)
- @contextmanager декоратор используется для превращения любой функции генератора в контекстный менеджер.
- yield работает как разделитель между частями _enter_ и _exit_ контекстного менеджера.
Обработка файлов
from contextlib import contextmanager
@contextmanager
def file_open(txt_file, mode):
open_file = open(txt_file, mode)
try:
yield open_file
except:
print("Произошла ошибка открытия файла")
finally:
open_file.close()
with file_open("temp.txt", "+a") as a:
content = a.readlines()
Я заключаю yield в блок try, потому что не знаю, что пользователь собирается делать с объектом open_file.
Мы только что рассмотрели введение в контекстные менеджеры, но я чувствую, что это только верхушка айсберга, и для них есть много интересных вариантов использования.
Поделиться записью в социальных сетях
Хорошая информация для ознакомление основами контекстного менеджера)