Что такое контекстный менеджер

В питоне есть оператор with. Размещенный внутри код выполняется с особенностью: до и после гарантированно срабатывают события входа в блок withи выхода из него. Объект, который определяет эту логику, называется контекстным менеджером.

События входа и выхода из блока определены методами __enter__ и __exit__. Первый срабатывает в тот момент, когда ход исполнения программы переходит внутрь with. Метод может вернуть значение. Оно будет доступно низлежащему внутри блока with коду.

__exit__ срабатывает в момент выхода из блока, в т.ч. и по причине исключения. В этом случае в метод будет передана тройка значений (exc_class, exc_instance, traceback).

Самый распространённый контекстный менеджер – класс, порожденный функцией open. Он гарантирует, что файл будет закрыт даже в том случае, если внутри блока возникнет ошибка.

Нужно стараться выходить из контекстного менеджера как можно быстрее, чтобы освобождать контекст и ресурсы.

with open('file.txt') as f:
    data = f.read()
process_data(data)

Пример реализации своего контекстного менеджера на основе класса:

class Printable:
    def __enter__(self):
        print('enter')

    def __exit__(self, type, value, traceback):
        print('exit')

Пример реализации своего контекстного менеджера с использованием встроенной библиотеки contextlib

from contextlib import contextmanager

@contextmanager
def printable():
    print('enter')
    try:
      yield
    finally:
      print('exit')

Контекстные менеджеры также можно использовать для временной замены параметров, переменных окружения, транзакций БД.

Oct. 10, 2023, Источник

Это конструкция with , которая предоставляет управление к доступу к некому ресурсу.

На контекстный менеджер возлагается 2 функции:

  • Что нужно сделать в момент когда необходим доступ к ресурсу
  • Что нужно сделать когда доступ уже не нужен

Встроенная функция open открывает и закрывает объект

Контекстные менеджеры упрощают запись блоков try-finally. Оператор with позволяет разработчикам писать свой код в сжатом и понятном виде.

Пример менеджера контекста для открытия файла

class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None

    def __enter__(self):
        try:
            self.file = open(self.filename, self.mode)
            return self.file
        except FileNotFoundError:
            print("Error: File not found")
            raise

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()


with FileManager("test.txt", "r") as f:
    if f:
        print(f.read())

 

Oct. 10, 2023, Источник

Менеджер контекста позволяет выделять и освобождать ресурсы строго по необходимости. В Python за это отвечает блок с оператором with. Он имеет следующую конструкцию:

[async] with <функция> as <переменная>:
    ...

Протокол контекстного менеджера

Контекстный менеджер должен иметь следующие методы:

  1. Если он синхронный - __enter__ и __exit__
  2. Если асинхронный - __aenter__ и __aexit__

В дальнейшем речь будет идти про синхронный контекстный менеджер, но ничего не мешает всё что указано ниже использовать с асинхронным контекстным менеджером (если не указано иное).

Обработка исключений

В случае возникновения исключения в метод __exit__ передается 3 параметра - type, value и traceback.

При обработке исключений важно то, что возвращает метод __exit__. Тут есть 2 варианта:

  1. В случае, если исключение было обработано нормально метод должен вернуть True.
  2. Возращение любого другого значения возбудит исключение, которое передадится на уровень выше.

Контекстный менеджер из генератора

Здесь на помощь к нам приходит contextlib. С помощью contextmanager мы можем реализовать менеджер контекста через декоратор и генераторы. Выглядит это так:

from contextlib import contextmanager

@contextmanager
def open_file(name):
    f = open(name, 'w')
    yield f
    f.close()

Работает это следующим образом:

  1. Python встречает ключевое слово yield, из-за чего создается генератор, а не функция. Генератор должен возвращать только одно значение, оно будет привязано к with.
  2. Тот в свою очередь реализует методы __enter__ и __exit__. Всё что до использования генератора попадает в __enter__, а после него - в __exit__
  3. В случае возникновения исключения оно вызывается внутри генератора, поэтому можно его словить и обработать при помощи конструкции `try ... except ...

Для асинхронных функций нужно использовать asynccontextmanager из того же contextlib

Oct. 24, 2023, notes.kiriha.ru