Контекстные менеджеры Python — использование и примеры
В этом уроке вы узнаете о менеджерах контекста (context manager) в Python и о том, как их создать и эффективно использовать.
- Что такое менеджеры контекста в Python?
- Оператор with в Python
- Протокол диспетчера контекста Python
- Метод __enter__()
- Метод __exit__()
- Приложения для управления контекстом Python
- 1) Открыть – Закрыть
- 2) Зафиксировать – отпустить
- 3) Старт – стоп
- 3) Изменить – сбросить
- Реализация протокола диспетчера контекста Python
- Использование контекстного менеджера Python для реализации шаблона запуска и остановки
Что такое менеджеры контекста в Python?
Менеджер контекста — это объект, который определяет контекст времени выполнения внутри оператора with.
Давайте начнем с простого примера, чтобы понять концепцию context manager. Предположим, у вас есть файл data.txt, содержащий целое число 100.
Следующая программа считывает файл data.txt, преобразует его содержимое в число и выводит результат на стандартный вывод:
f = open('data.txt') data = f.readlines() # convert the number to integer and display it print(int(data[0])) f.close()
Код прост и понятен.
Однако data.txt может содержать данные, которые невозможно преобразовать в число. В этом случае код приведет к исключению.
Например, если 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() # convert the number to integer and display it 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 в Python
Вот типичный синтаксис оператора with:
with context as ctx: # use the the object # context is cleaned up
Как это работает.
- Когда 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
Менеджеры контекста Python работают на основе context manager protocol.
Протокол контекстного менеджера имеет следующие методы:
- __enter__() — настроить контекст и при необходимости вернуть какой-либо объект
- __exit__() – очистить объект.
Если вы хотите, чтобы класс поддерживал протокол диспетчера контекста, вам необходимо реализовать эти два метода. Предположим, что ContextManager — это класс, поддерживающий протокол менеджера контекста.
Ниже показано, как использовать класс ContextManager:
with ContextManager() as ctx: # do something # done with the context
Когда вы используете класс ContextManager с оператором with, Python неявно создает экземпляр класса ContextManager(экземпляр) и автоматически вызывает метод __enter__() для этого экземпляра. Метод __enter__() может опционально возвращать объект. Если да, Python присваивает возвращаемому объекту значение ctx.
Обратите внимание, что ctx ссылается на объект, возвращаемый методом __enter__(). Он не ссылается на экземпляр класса ContextManager.
Если исключение возникает внутри блока with или после блока with, Python вызывает метод __exit__() для объекта экземпляра.
Функционально оператор with эквивалентен следующему оператору try…finally:
instance = ContextManager() ctx = instance.__enter__() try: # do something with the txt finally: # done with the context instance.__exit__()
Метод __enter__()
В методе __enter__() вы можете выполнить необходимые шаги для настройки контекста.
При желании вы можете вернуть объект из метода __enter__().
Метод __exit__()
Python всегда выполняет метод __exit__(), даже если в блоке with возникает исключение.
Метод __exit__() принимает три аргумента: exception type, exception value и traceback object. Все эти аргументы будут иметь значение None, если не произойдет никаких исключений.
def __exit__(self, ex_type, ex_value, ex_traceback): ...
Метод __exit__() возвращает логическое значение: True или False.
Если возвращаемое значение равно True, Python будет молчать о любых исключениях. В противном случае произойдет исключение.
Приложения для управления контекстом Python
Как видно из предыдущего примера, обычно менеджер контекста используется для автоматического открытия и закрытия файлов.
Однако вы можете использовать контекстные менеджеры во многих других случаях:
1) Открыть – Закрыть
Если вы хотите автоматически открывать и закрывать ресурс, вы можете использовать контекстный менеджер.
Например, вы можете открыть сокет и закрыть его с помощью контекстного менеджера.
2) Зафиксировать – отпустить
Менеджеры контекста могут помочь вам более эффективно управлять блокировками объектов. Они позволяют вам получить блокировку и автоматически снять ее.
3) Старт – стоп
Менеджеры контекста также помогают вам работать со сценарием, требующим фаз запуска и остановки.
Например, вы можете использовать контекстный менеджер, чтобы запускать таймер и автоматически останавливать его.
3) Изменить – сбросить
Менеджеры контекста могут работать со сценарием изменения и сброса.
Например, вашему приложению необходимо подключаться к нескольким источникам данных. И у него есть соединение по умолчанию.
Чтобы подключиться к другому источнику данных:
- Сначала используйте контекстный менеджер, чтобы изменить соединение по умолчанию на новое.
- Во-вторых, работайте с новым соединением.
- В-третьих, верните его к соединению по умолчанию после завершения работы с новым соединением.
Реализация протокола диспетчера контекста Python
Ниже показана простая реализация функции 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__().
Использование контекстного менеджера Python для реализации шаблона запуска и остановки
Следующее определяет класс 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 из модуля времени.
- Во-вторых, запустите таймер в методе __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)