Использование менеджера контекста в Python

Менеджеры контекста в Python

В этой статье вы узнаете что такое контекстный менеджер, и как он упрощает работу в 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.

Заключение

Мы только что рассмотрели введение в контекстные менеджеры, но я чувствую, что это только верхушка айсберга, и для них есть много интересных вариантов использования.

Егор Егоров

Программирую на Python с 2017 года. Люблю создавать контент, который помогает людям понять сложные вещи. Не представляю жизнь без непрерывного цикла обучения, спорта и чувства юмора.

Ссылка на мой github есть в шапке. Залетай.

Оцените автора
Егоров Егор
Добавить комментарий

  1. Анастасия

    Хорошая информация для ознакомление основами контекстного менеджера)

    Ответить
    1. Егор Егоров автор

      спасибо большое, я старался максимально простым языком написать 🙂

      Ответить