- Контекстный менеджер в Python
- Оператор with
- Синтаксис
- Как это работает
- Протокол контекстного менеджера
- Метод __enter__()
- Метод __exit__()
- Как можно использовать контекстный менеджер
- 1) Закрытие — открытие
- 2) Заблокировать — разблокировать
- 3) Запустить — остановить
- 4) Изменить — сбросить
- Создание протокола контекстного менеджера
- Как это работает
- Реализации шаблона запуска и остановки с помощью контекстного менеджера
- Как это работает
- Что нужно запомнить
Контекстный менеджер в 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.__file = open(self.filename, self.mode) return self.__file def __exit__(self, exc_type, exc_value, exc_traceback): print(f'Closing the file .') 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__() .
СodeСhick.io — простой и эффективный способ изучения программирования.
2023 © ООО «Алгоритмы и практика»