Контекстный менеджер в Python

В этой статье вы узнаете, что такое контекстный менеджер и как его эффективно использовать в Python.

Менеджер контекста — это объект, определяющий контекст выполнения в операторе with.

Давайте начнем с простого примера, чтобы понять концепцию менеджера контекста.

Предположим, что у yас есть файл data.txt, в котором содержится целое число 100.

Теперь напишем программу, которая читает файл data.txt, преобразует его содержимое в число и выводит результат на стандартный вывод:

f = open('data.txt')
data = f.readlines()

# преобразует содержимое в целое число и выводит его
print(int(data[0]))

f.close()

Код прост и понятен.

Однако что если в файле data.txt будут содержаться данные, которые не нельзя преобразовать в целое число? В таком случае возникнет исключение ValueError.

Например, если в файл data.txt записать строку '100' вместо числа 100, вы получите следующую ошибку:

ValueError: invalid literal for int() with base 10: "'100'"

Из-за этого исключения Python может не закрыть файл должным образом.

Чтобы исправить такое поведение, можно воспользоваться оператором try...except...finally:

try:
    f = open('data.txt')
    data = f.readlines()
    # преобразует содержимое в целое число и выводит его
    print(int(data[0]))
except ValueError as error:
    print(error)
finally:
    f.close()

Поскольку код в блоке finally всегда выполняется, программа всегда будет правильно закрывать файл.

Это решение работает как надо, но оно слишком многословное.

Поэтому в Python есть способ автоматически закрыть файл после завершения его обработки — и он менее многословный.

Здесь в игру вступают менеджеры контекста.

Ниже показано, как использовать менеджер контекста для обработки файла data.txt:

with open('data.txt') as f:
    data = f.readlines()
    print(int(data[0])    

В этом примере мы используем функцию open() с оператором with. После блока with Python автоматически закроется.

Оператор with

Синтаксис

with context as ctx:
    # используем объект 
# очищаем контекст

Как это работает

  • Когда Python встречает оператор with, он создает новый контекст. При желании контекст может возвращать объект.
  • После блока with Python автоматически очищает контекст.
  • Область видимости ctx имеет ту же область видимости, что и оператор with. Это означает, что вы можете обращаться к ctx как внутри оператора with, так и после него.

Ниже показано, как получить доступ к переменной f после оператора with:

with open('data.txt') as f:
    data = f.readlines()
    print(int(data[0]))


print(f.closed)  # Вывод: True

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

Контекстные менеджеры Python работают на основе протокола контекстного менеджера.

Протокол менеджера контекста включает следующие методы:

  • __enter__() — устанавливает контекст и, по желанию, возвращать некоторый объект.
  • __exit__() — очищает объект.

Если вы хотите, чтобы класс поддерживал протокол контекстного менеджера, вам необходимо реализовать эти два метода.

Предположим, что у нас есть некий классContextManager, поддерживающий протокол контекстного менеджера.

Вот, как можно использовать этот класс:

with ContextManager() as ctx:
    # что-то делаем
# закончили с контекстом

Когда вы используете класс ContextManager с оператором with, Python неявно создает экземпляр класса — instance — и автоматически вызывает метод __enter__() на этом экземпляре.

Метод __enter__() может по желанию возвращать объект. Если это так, Python присваивает возвращаемый объект ctx.

Обратите внимание, что ctx ссылается на объект, возвращаемый методом __enter__(). Он не ссылается на экземпляр класса ContextManager.

Если внутри блока with или после блока with возникает исключение, Python вызывает метод __exit__() на объекте экземпляра.

Функционально оператор with эквивалентен конструкции try...finally:

instance = ContextManager()
ctx = instance.__enter__()

try:
    # что-то делаем с txt
finally:
    # закончили с контекстом
    instance.__exit__()

Метод __enter__()

В методе __enter__() можно выполнить необходимые действия по настройке контекста.

При необходимости вы можете вернуть объект из метода __enter__().

Метод __exit__()

Python всегда выполняет метод __exit__(), даже если в блоке with возникает исключение.

Метод __exit__() принимает три аргумента: тип исключения, значение исключения и объект трассировки. Все эти аргументы будут равны None, если исключение не произошло.

def __exit__(self, ex_type, ex_value, ex_traceback):
    ...

Метод __exit__() возвращает логическое значение: True или False.

Если возвращаемое значение равно True, Python заглушит исключение. 

Как можно использовать контекстный менеджер

В этой статье мы уже выяснили, что контекстный менеджер можно использовать для автоматического открытия и закрытия файлов.

Давайте разберемся, в каких случаях еще можно использовать контекстный менеджер. Вот некоторые из них:

1) Закрытие — открытие

Если вы хотите открывать и закрывать ресурс автоматически, вы можете использовать контекстный менеджер.

Например, вы можете открыть сокет и закрыть его с помощью контекстного менеджера.

2) Заблокировать — разблокировать

Контекстные менеджеры помогут вам более эффективно управлять блокировками объектов.

3) Запустить — остановить

Контекстные менеджеры помогут вам работать со сценариями, требующими запуска и остановки.

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

4) Изменить — сбросить

Контекстные менеджеры могут работать со сценариями изменения и сброса.

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

Вот алгоритм для подключения к другому источнику данных:

  • Используйте контекстный менеджер для изменения соединения по умолчанию на новое.
  • Работайте с новым соединением.
  • После завершения работы с новым соединением верните его обратно к соединению по умолчанию.

Создание протокола контекстного менеджера

Ниже показана простая реализация функции open() с использованием протокола контекстного менеджера:

class File:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        print(f'Opening the file {self.filename}.')
        self.__file = open(self.filename, self.mode)
        return self.__file

    def __exit__(self, exc_type, exc_value, exc_traceback):
        print(f'Closing the file {self.filename}.')
        if not self.__file.closed:
            self.__file.close()

        return False


with File('data.txt', 'r') as f:
    print(int(next(f)))

Как это работает

  • Инициализируем имя файла и режим в методе __init__().
  • Открываем файл в методе __enter__() и возвращаем объект файла.
  • Закрываем файл, если он открыт, в методе __exit__().

Реализации шаблона запуска и остановки с помощью контекстного менеджера

Давайте создадим класс Timer, который поддерживает протокол контекстного менеджера:

from time import perf_counter


class Timer:
    def __init__(self):
        self.elapsed = 0

    def __enter__(self):
        self.start = perf_counter()
        return self

    def __exit__(self, exc_type, exc_value, exc_traceback):
        self.stop = perf_counter()
        self.elapsed = self.stop - self.start
        return False

Как это работает

  • Импортируем perf_counter из модуля time.
  • Запускаем таймер в методе __enter__().
  • Останавливаем таймер в методе __exit__() и верните прошедшее время.

Теперь вы можете использовать класс Timer для измерения времени, необходимого для вычисления числа Фибоначчи, равного 1000, один миллион раз:

def fibonacci(n):
    f1 = 1
    f2 = 1
    for i in range(n-1):
        f1, f2 = f2, f1 + f2

    return f1


with Timer() as timer:
    for _ in range(1, 1000000):
        fibonacci(1000)

print(timer.elapsed)

Что нужно запомнить

  • Используйте менеджеры контекстов для определения контекстов времени выполнения при выполнении в операторе with.
  • Для поддержки протокола менеджера контекста нужно реализовать методы __enter__() и __exit__().
codechick

СodeСhick.io - простой и эффективный способ изучения программирования.

2024 ©