Context Manager (менеджер контекста)
Управляющий контекстом (он же менеджер) следит за инициализацией и финализацией данного контекста, в частности определяет действия которые должны происходить до и после выполнения блока кода.
Обычно используется совместно с инструкцией with, позволяющей выполнение описанного в ней блока в некотором контексте исполнения. Однако не возбраняется и прямое обращение к методам менеджера.
- сохранением и восстановлением глобального состояния;
- блокировкой и разблокировкой ресурсов;
- открытием и закрытиям файлов.
На заметку
В стандартной поставке Python имеются встроенные менеджеры контекста для таких задач как: синхронизация нитей, закрытие файлов и других объектов, упрощенное управление контекстом десятичной арифметики. См. модуль contextlib.
Удобным инструментом для реализации протокола менеджера являются генераторы и декоратор contextlib.contextmanager . Если функцию-генератор обернуть в данный декоратор, то вместо итератора вернётся менеджер контекста, с уже определёнными методами __enter__() и __exit__() .
Менеджеры контекста (оператор «with»)
Менеджер контекста является объектом , который получает уведомление , когда контекст (блок кода) начинается и заканчивается. Вы обычно используете один с with заявлением. Он заботится об уведомлении.
Например, файловые объекты являются контекстными менеджерами. Когда контекст заканчивается, объект файла закрывается автоматически:
open_file = open(filename) with open_file: file_contents = open_file.read() # the open_file object has automatically been closed.
В приведенном выше примере, как правило , упрощена, используя в as ключевого слова:
with open(filename) as open_file: file_contents = open_file.read() # the open_file object has automatically been closed.
Все, что завершает выполнение блока, вызывает метод exit менеджера контекста. Это включает исключения и может быть полезно, когда ошибка приводит к преждевременному выходу из открытого файла или соединения. Выход из сценария без надлежащего закрытия файлов / соединений — плохая идея, которая может привести к потере данных или другим проблемам. С помощью диспетчера контекста вы можете всегда принимать меры предосторожности, чтобы предотвратить повреждение или потерю таким образом. Эта функция была добавлена в Python 2.5.
Присвоение цели
Многие контекстные менеджеры возвращают объект при вводе. Вы можете назначить этот объект на новое имя в with заявлением.
Например, с помощью подключения к базе данных в with утверждением может дать вам объект курсора:
with database_connection as cursor: cursor.execute(sql_query)
Файловые объекты возвращаются сами по себе, это позволяет как открыть объект файла, так и использовать его в качестве диспетчера контекста в одном выражении:
with open(filename) as open_file: file_contents = open_file.read()
Написание собственного менеджера контекста
Менеджер контекста является любой объект , который реализует два магических методов __enter__() и __exit__() (хотя он может реализовать другие методы, а):
class AContextManager(): def __enter__(self): print("Entered") # optionally return an object return "A-instance" def __exit__(self, exc_type, exc_value, traceback): print("Exited" + (" (with an exception)" if exc_type else "")) # return True if you want to suppress the exception
Если контекст выходит с исключением, информация о том , что исключение будет передан как тройной exc_type , exc_value , traceback (это те же переменные , как возвращаемое sys.exc_info() функции). Если контекст выходит нормально, все три из этих аргументов не будет None .
Если исключение происходит , и передается в __exit__ метод, метод может возвращать True , чтобы подавить исключение, или исключение будет вновь поднят в конце __exit__ функции.
with AContextManager() as a: print("a is %r" % a) # Entered # a is 'A-instance' # Exited with AContextManager() as a: print("a is %d" % a) # Entered # Exited (with an exception) # Traceback (most recent call last): # File "", line 2, in # TypeError: %d format: a number is required, not str
Следует отметить , что во втором примере , даже если исключение происходит в середине тела с-заявлением __exit__ обработчик еще запускается на выполнение, до того , как исключение распространяется на внешнюю область.
Если вам необходим только __exit__ метод, вы можете вернуть экземпляр менеджера контекста:
class MyContextManager: def __enter__(self): return self def __exit__(self): print('something')
Написание собственного менеджера контекста с использованием синтаксиса генератора
Также можно написать менеджер контекста с использованием синтаксиса генератора благодаря contextlib.contextmanager декоратора:
import contextlib @contextlib.contextmanager def context_manager(num): print('Enter') yield num + 1 print('Exit') with context_manager(2) as cm: # the following instructions are run when the 'yield' point of the context # manager is reached. # 'cm' will have the value that was yielded print('Right in the middle with cm = <>'.format(cm))
Enter Right in the middle with cm = 3 Exit
Декоратор упрощает задачу написания менеджера контекста путем преобразования генератора в один. Все , что прежде , чем выражение выхода становится __enter__ метод, значение дало становится значение , возвращаемым генератор (который может быть связан с переменным в отчете с), и все , что после того, как выражение выхода становится __exit__ методом.
Если исключение должно быть обработано менеджером контекста, A try..except..finally -блок может быть записана в генераторе и каких — либо исключений , затронутые в with -блоком будут обработаны этим блоком исключений.
@contextlib.contextmanager def error_handling_context_manager(num): print("Enter") try: yield num + 1 except ZeroDivisionError: print("Caught error") finally: print("Cleaning up") print("Exit") with error_handling_context_manager(-1) as cm: print("Dividing by cm = <>".format(cm)) print(2 / cm)
Enter Dividing by cm = 0 Caught error Cleaning up Exit
Несколько контекстных менеджеров
Вы можете открыть несколько менеджеров контента одновременно:
with open(input_path) as input_file, open(output_path, 'w') as output_file: # do something with both files. # e.g. copy the contents of input_file into output_file for line in input_file: output_file.write(line + '\n')
Это имеет тот же эффект, что и вложенные контекстные менеджеры:
with open(input_path) as input_file: with open(output_path, 'w') as output_file: for line in input_file: output_file.write(line + '\n')
Управление ресурсами
class File(): def __init__(self, filename, mode): self.filename = filename self.mode = mode def __enter__(self): self.open_file = open(self.filename, self.mode) return self.open_file def __exit__(self, *args): self.open_file.close()
__init__() метод устанавливает объект, в данном случае настраивает имя файла и режим открытия файла. __enter__() открывает и возвращает файл и __exit__() просто закрывает его.
Используя эти магические методы ( __enter__ , __exit__ ) позволяет реализовать объекты , которые могут быть легко использованы with с утверждением.
Используйте класс File:
for _ in range(10000): with File('foo.txt', 'w') as f: f.write('foo')
Менеджеры контекста — Python: Основы текстового ввода-вывода
Ручное закрытие файлов, а также отдача закрытия на откуп среде исполнения, обладают одним существенным недостатком: если между открытием файла на запись и его закрытием произойдёт ошибка, то в лучшем случае файл окажется открыт слишком долго (может остаться ссылка), а в худшем случае не сохранится часть данных.
Хочется иметь возможность автоматически закрывать файл сразу после окончания работы с ним и осуществлять закрытие даже при возникновении ошибки. Файловые объекты уже умеют работать в таком режиме, но для этого их нужно использовать как менеджеры контекста.
Краткое введение в менеджеры контекста
Менеджер контекста (context manager) — это некий объект, реализующий одноимённый протокол (да, кругом протоколы!). Объекты, реализующие этот протокол, позволяют использовать следующий специальный синтаксис:
with object as foo: # Здесь нам доступен ресурс foo. # Это тело with-блока. # А здесь ресурс foo уже освобождён, # даже если в теле with-блока произошла ошибка.
Весь код в теле with -блока работает «в контексте». Чаще всего контекст подразумевает выделение некоего ресурса, например, файла. По выходу из контекста ресурс автоматически освобождается, даже если при выполнении блока возникло исключение. То, что нам нужно!
Использование файлов как менеджеров контекста
Рассмотрим сразу комплексный пример. Для чего перепишем скрипт нумерации строк файла из предыдущего урока:
with open("input.txt", "r") as input_file: with open("output.txt", "w") as output_file: for i, line in enumerate(input_file, 1): output_file.write( f"i>) line>" )
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Менеджер контекста это просто
« Контекстные менеджеры в Python — это удивительный механизм, который позволяет гарантировать корректное управление ресурсами и обеспечивать безопасное выполнение кода.» — Гвидо ван Россум, создатель языка программирования Python.
Что же удивительного в в этом механизме?
Для того, чтобы стало ясно, зачем эта вещь обратимся к избитому примеры работы с файлами. Новичок, который стал недавно программировать даже не задумывается о том, что оперативная память компьютера небезгранична, более опытные знакомы с такими вещами и давно используют контекстный менеджер, профессионалы могут реализовать свой (рассмотри этот вопрос позже).
Итак, приступим к открытию файла, делается это просто. Как это делают новички:
file = open("file.txt", "r") try: # Действия с файлом content = file.read() print(content) finally: file.close()
В этом примере мы используем функцию open() для открытия файла «file.txt» в режиме чтения («r»). Затем мы выполняем необходимые операции с файлом, в данном случае читаем его содержимое и выводим на экран. В блоке finally мы закрываем файл, чтобы освободить системные ресурсы, даже в случае возникновения исключений. Уже лучше, но сколько лишнего кода! Однако, код ниже является рекомендуемым.
В обоих случаях можно ненароком забыть о close() . Проблема решается простым использованием оператора with :
with open("file.txt", "r") as file: content = file.read() print(content)
После чтения файла автоматически будет вызван метод close() . Как все просто, правда?
Использование контекстного менеджера в других областях
Использование контекстного менеджера в Python для работы с базами данных (БД) и потоками может облегчить управление ресурсами и обеспечить безопасное выполнение операций. Ниже приведены примеры использования контекстных менеджеров для работы с БД и потоками в Python.
import sqlite3 # Пример работы с SQLite базой данных with sqlite3.connect('example.db') as conn: cursor = conn.cursor() # Выполнение операций с базой данных cursor.execute('SELECT * FROM table_name') result = cursor.fetchall() print(result)
В этом примере контекстный менеджер sqlite3.connect() используется для установления соединения с SQLite базой данных. Затем выполняются операции с базой данных с использованием курсора. После завершения блока with соединение будет автоматически закрыто, освобождая ресурсы.
Работа с потоками:
import threading # Пример работы с потоками def worker(): print("Работник выполняет задачу. ") with threading.Lock() as lock: # Блокировка ресурса перед выполнением задачи lock.acquire() try: t = threading.Thread(target=worker) t.start() finally: # Освобождение ресурса после выполнения задачи lock.release()
В этом примере контекстный менеджер threading.Lock() используется для блокировки ресурса перед выполнением задачи в отдельном потоке. Выполнение кода в блоке try происходит внутри захваченной блокировки, а после завершения блока with ресурс (блокировка) будет автоматически освобожден.
Контекстные менеджеры обеспечивают удобный и безопасный способ работы с ресурсами, такими как базы данных и потоки, упрощая управление и гарантируя корректное освобождение ресурсов после использования.
Сетевые соединения:
Использование контекстного менеджера с сетевыми соединениями в Python обеспечивает удобство и безопасность при работе с сетевыми ресурсами. Вот пример использования контекстного менеджера для работы с сетевым соединением с использованием модуля socket :
import socket with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect(('example.com', 80)) # Выполнение операций с сетевым соединением s.sendall(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') response = s.recv(1024) print(response.decode())
В этом примере контекстный менеджер socket.socket() используется для создания сокета для TCP-соединения ( socket.SOCK_STREAM ) с использованием семейства адресов IPv4 ( socket.AF_INET ). Затем, в блоке with , мы устанавливаем соединение с удаленным хостом, отправляем HTTP-запрос и получаем ответ от сервера. После завершения блока with сокет будет автоматически закрыт, освобождая ресурсы.
Это простой пример, но контекстные менеджеры могут также использоваться для более сложных сценариев работы с сетевыми соединениями, такими как установка защищенного соединения с помощью SSL/TLS или использование протокола UDP.
Использование контекстного менеджера для работы с сетевыми соединениями облегчает управление ресурсами, обеспечивает надежность и гарантирует корректное закрытие соединения после использования, что особенно важно для предотвращения утечек ресурсов.
Как реализовать свой контекстный менеджер
Чтобы создать свой контекстный менеджер в Python, вам необходимо определить класс, который содержит методы __enter__() и __exit__() .
Метод __enter__() выполняется перед выполнением блока кода внутри оператора with . Он может выполнять какие-либо подготовительные действия или возвращать значение, которое будет связано с переменной после ключевого слова as .
Метод __exit__() вызывается после завершения выполнения блока кода with . Он используется для выполнения завершающих действий, таких как освобождение ресурсов, обработка исключений или выполнение финализирующих операций.
Вот пример простого контекстного менеджера, который записывает время выполнения блока кода:
import time class Timer: def __enter__(self): self.start_time = time.time() return self def __exit__(self, exc_type, exc_val, exc_tb): elapsed_time = time.time() - self.start_time print(f"Elapsed time: seconds") # Пример использования контекстного менеджера with Timer() as timer: # Ваш блок кода time.sleep(2)
Также можно даже реализовать асинхронный контекстный менеджер:
import asyncio class AsyncTimer: def __enter__(self): self.start_time = asyncio.get_event_loop().time() return self def __aexit__(self, exc_type, exc_val, exc_tb): elapsed_time = asyncio.get_event_loop().time() - self.start_time print(f"Elapsed time: seconds") # Пример использования асинхронного контекстного менеджера async def example(): async with AsyncTimer() as timer: # Ваш асинхронный блок кода await asyncio.sleep(2) # Запуск асинхронной функции asyncio.run(example())
Асинхронный контекстный менеджер используется в асинхронном программировании для управления ресурсами и выполнения финализирующих операций в асинхронных окружениях. Он предоставляет асинхронные версии методов __enter__() и __exit__() , которые могут быть использованы в блоке async with для автоматического управления ресурсами и обработки ошибок.
Вот несколько причин, почему асинхронный контекстный менеджер может быть полезным:
- Управление асинхронными ресурсами: Асинхронный контекстный менеджер позволяет управлять асинхронными ресурсами, такими как соединения с базами данных, сетевые соединения или файловые дескрипторы. Он обеспечивает автоматическое открытие и закрытие ресурсов, что упрощает их использование и предотвращает утечки ресурсов.
- Асинхронные операции пред- и пост-обработки: Асинхронный контекстный менеджер позволяет выполнить асинхронные операции перед выполнением блока кода async with (в методе __enter__() ), а также после завершения блока (в методе __aexit__() ). Это может быть полезно для инициализации, очистки или финализации ресурсов, асинхронных вычислений или обработки ошибок.
- Обработка исключений: Асинхронный контекстный менеджер предоставляет механизм для обработки исключений, возникающих в асинхронном блоке кода. Метод __aexit__() может быть использован для ловли исключений, их обработки и выполнения соответствующих действий, например, для отката транзакции или восстановления состояния.
В целом, асинхронный контекстный менеджер предоставляет удобный и безопасный способ управления асинхронными ресурсами и выполнения дополнительных операций перед и после асинхронного блока кода. Он способствует более чистому и структурированному асинхронному программированию и помогает избежать утечек ресурсов и необработанных исключений.
Заключение
Контекстные менеджеры являются важной концепцией в Python, которая обеспечивает удобство, безопасность и эффективность при работе с ресурсами, файлами, базами данных, сетевыми соединениями и другими объектами, требующими специального управления жизненным циклом.
- with
- контекстыные менеджеры