contextlib.md
1> **Источник:** https://python-all.ru/3.7/library/contextlib.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# `contextlib` – Утилиты для контекстов оператора `with`89**Исходный код:** [Lib/contextlib.py](https://python-all.ru/src/3.7/Lib/contextlib.py)1011---1213Этот модуль предоставляет утилиты для типовых задач, связанных с оператором [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with). Для получения дополнительной информации см. также [Типы менеджеров контекста](https://python-all.ru/3.7/library/stdtypes.html#typecontextmanager) и [Менеджеры контекста оператора with](https://python-all.ru/3.7/reference/datamodel.html#context-managers).1415## Утилиты1617Предоставляемые функции и классы:1819#### `class contextlib.AbstractContextManager`2021[абстрактный базовый класс](https://python-all.ru/3.7/glossary.html#term-abstract-base-class) для классов, реализующих [`object.__enter__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__enter__) и [`object.__exit__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__exit__). Предоставляется реализация по умолчанию для [`object.__enter__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__enter__), которая возвращает `self`, а [`object.__exit__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__exit__) – это абстрактный метод, который по умолчанию возвращает `None`. См. также определение [Типов менеджеров контекста](https://python-all.ru/3.7/library/stdtypes.html#typecontextmanager).2223Новое в версии 3.6.2425#### `class contextlib.AbstractAsyncContextManager`2627[абстрактный базовый класс](https://python-all.ru/3.7/glossary.html#term-abstract-base-class) для классов, реализующих [`object.__aenter__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__aenter__) и [`object.__aexit__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__aexit__). Предоставляется реализация по умолчанию для [`object.__aenter__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__aenter__), которая возвращает `self`, а [`object.__aexit__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__aexit__) – это абстрактный метод, который по умолчанию возвращает `None`. См. также определение [Асинхронных менеджеров контекста](https://python-all.ru/3.7/reference/datamodel.html#async-context-managers).2829Добавлено в версии 3.7.3031#### `@contextlib.contextmanager`3233Эта функция – [декоратор](https://python-all.ru/3.7/glossary.html#term-decorator), который можно использовать для определения фабричной функции для контекстных менеджеров оператора [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with), без необходимости создавать класс или отдельные методы [`__enter__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__enter__) и [`__exit__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__exit__).3435Хотя многие объекты изначально поддерживают использование в with-выражениях, иногда требуется управлять ресурсом, который сам по себе не является менеджером контекста, и не реализует метод `close()` для использования с `contextlib.closing`3637Абстрактным примером может служить следующий код для обеспечения корректного управления ресурсами:3839```python40from contextlib import contextmanager4142@contextmanager43def managed_resource(*args, **kwds):44 # Код для получения ресурса, например:45 resource = acquire_resource(*args, **kwds)46 try:47 yield resource48 finally:49 # Код для освобождения ресурса, например:50 release_resource(resource)5152>>> with managed_resource(timeout=3600) as resource:53... # Ресурс освобождается в конце этого блока,54... # даже если код в блоке вызывает исключение55```5657Декорируемая функция при вызове должна возвращать [генератор](https://python-all.ru/3.7/glossary.html#term-generator)-итератор. Этот итератор должен возвращать ровно одно значение, которое будет связано с целями в предложении `as` оператора [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with), если таковые имеются.5859В момент, где генератор делает yield, выполняется блок, вложенный в оператор [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with). Затем генератор возобновляется после выхода из блока. Если в блоке возникает необработанное исключение, оно повторно возбуждается внутри генератора в точке, где произошел yield. Таким образом, можно использовать оператор [`try`](https://python-all.ru/3.7/reference/compound_stmts.html#try)…[`except`](https://python-all.ru/3.7/reference/compound_stmts.html#except)…[`finally`](https://python-all.ru/3.7/reference/compound_stmts.html#finally) для перехвата ошибки (если есть) или для выполнения очистки. Если исключение перехватывается только для того, чтобы зарегистрировать его или выполнить какое-либо действие (а не подавить полностью), генератор должен повторно возбудить это исключение. В противном случае контекстный менеджер генератора сообщит оператору `with`, что исключение обработано, и выполнение продолжится с оператора, следующего сразу за оператором `with`.6061[`contextmanager()`](https://python-all.ru/3.7/library/contextlib.html#contextlib.contextmanager) использует [`ContextDecorator`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ContextDecorator), поэтому создаваемые им контекстные менеджеры могут использоваться как в качестве декораторов, так и в операторах [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with). При использовании в качестве декоратора для каждого вызова функции неявно создается новый экземпляр генератора (это позволяет в противном случае «одноразовым» контекстным менеджерам, созданным с помощью [`contextmanager()`](https://python-all.ru/3.7/library/contextlib.html#contextlib.contextmanager), соответствовать требованию, согласно которому контекстные менеджеры должны поддерживать многократные вызовы, чтобы их можно было использовать в качестве декораторов).6263Изменено в версии 3.2: Использование [`ContextDecorator`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ContextDecorator).6465#### `@contextlib.asynccontextmanager`6667Аналог [`contextmanager()`](https://python-all.ru/3.7/library/contextlib.html#contextlib.contextmanager), но создает [асинхронный контекстный менеджер](https://python-all.ru/3.7/reference/datamodel.html#async-context-managers).6869Эта функция – [декоратор](https://python-all.ru/3.7/glossary.html#term-decorator), который можно использовать для определения фабричной функции для асинхронных контекстных менеджеров оператора [`async with`](https://python-all.ru/3.7/reference/compound_stmts.html#async-with), без необходимости создавать класс или отдельные методы [`__aenter__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__aenter__) и [`__aexit__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__aexit__). Она должна применяться к функции [асинхронного генератора](https://python-all.ru/3.7/glossary.html#term-asynchronous-generator).7071Простой пример:7273```python74from contextlib import asynccontextmanager7576@asynccontextmanager77async def get_connection():78 conn = await acquire_db_connection()79 try:80 yield conn81 finally:82 await release_db_connection(conn)8384async def get_all_users():85 async with get_connection() as conn:86 return conn.query('SELECT ...')87```8889Добавлено в версии 3.7.9091#### `contextlib.closing(thing)`9293Возвращает контекстный менеджер, который закрывает *thing* после завершения блока. По сути эквивалентно следующему:9495```python96from contextlib import contextmanager9798@contextmanager99def closing(thing):100 try:101 yield thing102 finally:103 thing.close()104```105106И позволяет писать код следующим образом:107108```python109from contextlib import closing110from urllib.request import urlopen111112with closing(urlopen('http://www.python.org')) as page:113 for line in page:114 print(line)115```116117без необходимости явно закрывать `page`. Даже если произойдет ошибка, `page.close()` будет вызван при выходе из блока [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with).118119#### `contextlib.nullcontext(enter_result=None)`120121Возвращает менеджер контекста, который возвращает *enter\_result* из `__enter__`, но в остальном ничего не делает. Предназначен для использования в качестве замены опционального менеджера контекста, например:122123```python124def myfunction(arg, ignore_exceptions=False):125 if ignore_exceptions:126 # Используйте suppress, чтобы игнорировать все исключения.127 cm = contextlib.suppress(Exception)128 else:129 # Не игнорируйте никакие исключения, cm не оказывает эффекта.130 cm = contextlib.nullcontext()131 with cm:132 # Выполнить действие133```134135Пример использования *enter\_result*:136137```python138def process_file(file_or_path):139 if isinstance(file_or_path, str):140 # Если строка, открыть файл141 cm = open(file_or_path)142 else:143 # Вызывающий отвечает за закрытие файла144 cm = nullcontext(file_or_path)145146 with cm as file:147 # Выполнить обработку файла148```149150Добавлено в версии 3.7.151152#### `contextlib.suppress(*exceptions)`153154Возвращает менеджер контекста, который подавляет любые из указанных исключений, если они возникают в теле оператора with, а затем возобновляет выполнение с первого оператора после конца оператора with.155156Как и любой другой механизм, полностью подавляющий исключения, этот менеджер контекста следует использовать только для обработки очень специфических ошибок, когда молчаливое продолжение выполнения программы является правильным решением.157158Например:159160```python161from contextlib import suppress162163with suppress(FileNotFoundError):164 os.remove('somefile.tmp')165166with suppress(FileNotFoundError):167 os.remove('someotherfile.tmp')168```169170Этот код эквивалентен:171172```python173try:174 os.remove('somefile.tmp')175except FileNotFoundError:176 pass177178try:179 os.remove('someotherfile.tmp')180except FileNotFoundError:181 pass182```183184Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.7/library/contextlib.html#reentrant-cms).185186Новое в версии 3.4.187188#### `contextlib.redirect_stdout(new_target)`189190Менеджер контекста для временного перенаправления [`sys.stdout`](https://python-all.ru/3.7/library/sys.html#sys.stdout) в другой файл или файлоподобный объект.191192Этот инструмент добавляет гибкость существующим функциям или классам, чей вывод жестко привязан к stdout.193194Например, вывод [`help()`](https://python-all.ru/3.7/library/functions.html#help) обычно направляется в *sys.stdout*. Вы можете захватить этот вывод в строку, перенаправив вывод в [`io.StringIO`](https://python-all.ru/3.7/library/io.html#io.StringIO) объект:195196```python197f = io.StringIO()198with redirect_stdout(f):199 help(pow)200s = f.getvalue()201```202203Чтобы отправить вывод [`help()`](https://python-all.ru/3.7/library/functions.html#help) в файл на диске, перенаправьте вывод в обычный файл:204205```python206with open('help.txt', 'w') as f:207 with redirect_stdout(f):208 help(pow)209```210211Чтобы отправить вывод [`help()`](https://python-all.ru/3.7/library/functions.html#help) в *sys.stderr*:212213```python214with redirect_stdout(sys.stderr):215 help(pow)216```217218Обратите внимание, что глобальный побочный эффект на [`sys.stdout`](https://python-all.ru/3.7/library/sys.html#sys.stdout) означает, что этот менеджер контекста не подходит для использования в библиотечном коде и большинстве многопоточных приложений. Он также не влияет на вывод подпроцессов. Тем не менее, это всё ещё полезный подход для многих вспомогательных скриптов.219220Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.7/library/contextlib.html#reentrant-cms).221222Новое в версии 3.4.223224#### `contextlib.redirect_stderr(new_target)`225226Аналогично [`redirect_stdout()`](https://python-all.ru/3.7/library/contextlib.html#contextlib.redirect_stdout), но перенаправляет [`sys.stderr`](https://python-all.ru/3.7/library/sys.html#sys.stderr) в другой файл или файлоподобный объект.227228Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.7/library/contextlib.html#reentrant-cms).229230Новое в версии 3.5.231232#### `class contextlib.ContextDecorator`233234Базовый класс, который позволяет менеджеру контекста также использоваться в качестве декоратора.235236Менеджеры контекста, наследующие от `ContextDecorator`, должны обычным образом реализовывать `__enter__` и `__exit__`. `__exit__` сохраняет необязательную обработку исключений даже при использовании в качестве декоратора.237238`ContextDecorator` используется [`contextmanager()`](https://python-all.ru/3.7/library/contextlib.html#contextlib.contextmanager), поэтому эта функциональность доступна автоматически.239240Пример `ContextDecorator`:241242```python243from contextlib import ContextDecorator244245class mycontext(ContextDecorator):246 def __enter__(self):247 print('Starting')248 return self249250 def __exit__(self, *exc):251 print('Finishing')252 return False253254>>> @mycontext()255... def function():256... print('The bit in the middle')257...258>>> function()259Starting260The bit in the middle261Finishing262263>>> with mycontext():264... print('The bit in the middle')265...266Starting267The bit in the middle268Finishing269```270271Это изменение – всего лишь синтаксический сахар для любой конструкции следующего вида:272273```python274def f():275 with cm():276 # Выполнить действия277```278279`ContextDecorator` позволяет вместо этого писать:280281```python282@cm()283def f():284 # Выполнить действия285```286287Становится ясно, что `cm` применяется ко всей функции, а не только к её части (и экономия уровня отступа тоже приятна).288289Существующие менеджеры контекста, уже имеющие базовый класс, можно расширить, используя `ContextDecorator` в качестве примеси (mixin):290291```python292from contextlib import ContextDecorator293294class mycontext(ContextBaseClass, ContextDecorator):295 def __enter__(self):296 return self297298 def __exit__(self, *exc):299 return False300```301302> **Примечание**303>304> Поскольку декорированная функция должна допускать многократный вызов, нижележащий менеджер контекста должен поддерживать использование в нескольких операторах [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with). Если это не так, следует использовать исходную конструкцию с явным оператором `with` внутри функции.305306Новое в версии 3.2.307308#### `class contextlib.ExitStack`309310Менеджер контекста, предназначенный для упрощения программного комбинирования других менеджеров контекста и функций очистки, особенно тех, которые являются необязательными или определяются входными данными.311312Например, набор файлов можно легко обработать в одном операторе with следующим образом:313314```python315with ExitStack() as stack:316 files = [stack.enter_context(open(fname)) for fname in filenames]317 # Все открытые файлы будут автоматически закрыты в конце318 # оператор with, даже если попытки открыть файлы, идущие далее319 # в списке, вызывают исключение320```321322Каждый экземпляр поддерживает стек зарегистрированных колбэков, которые вызываются в обратном порядке при закрытии экземпляра (явно или неявно в конце оператора [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with)). Обратите внимание, что колбэки *не* вызываются неявно при сборке мусора экземпляра стека контекста.323324Такая стековая модель используется, чтобы менеджеры контекста, получающие свои ресурсы в методе `__init__` (например, файловые объекты), могли обрабатываться корректно.325326Поскольку зарегистрированные колбэки вызываются в порядке, обратном регистрации, в итоге это ведёт себя так, как если бы с зарегистрированным набором колбэков использовалось несколько вложенных операторов [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with). Это распространяется даже на обработку исключений: если внутренний колбэк подавляет или заменяет исключение, то внешним колбэкам будут переданы аргументы, основанные на этом обновлённом состоянии.327328Это относительно низкоуровневый API, который заботится о деталях корректной размотки стека колбэков выхода. Он предоставляет подходящую основу для менеджеров контекста более высокого уровня, которые манипулируют стеком выхода специфичным для приложения образом.329330Новое в версии 3.3.331332#### `enter_context(cm)`333334Входит в новый менеджер контекста и добавляет его метод [`__exit__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__exit__) в стек колбэков. Возвращаемое значение – результат собственного метода [`__enter__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__enter__) менеджера контекста.335336Эти менеджеры контекста могут подавлять исключения точно так же, как они это делали бы при непосредственном использовании в операторе [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with).337338#### `push(exit)`339340Добавляет метод [`__exit__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__exit__) менеджера контекста в стек колбэков.341342Поскольку `__enter__` *не* вызывается, этот метод можно использовать для покрытия части реализации [`__enter__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__enter__) собственным методом [`__exit__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__exit__) менеджера контекста.343344Если передан объект, не являющийся менеджером контекста, этот метод считает его колбэком с той же сигнатурой, что и метод [`__exit__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__exit__) менеджера контекста, и добавляет его напрямую в стек колбэков.345346Возвращая истинные значения, эти колбэки могут подавлять исключения так же, как это делают методы [`__exit__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__exit__) менеджеров контекста.347348Переданный объект возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.349350#### `callback(callback, *args, **kwds)`351352Принимает произвольную функцию-колбэк и аргументы и добавляет её в стек колбэков.353354В отличие от других методов, колбэки, добавленные таким образом, не могут подавлять исключения (поскольку им никогда не передаются сведения об исключении).355356Переданный колбэк возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.357358#### `pop_all()`359360Перемещает стек колбэков в новый экземпляр [`ExitStack`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ExitStack) и возвращает его. При этой операции колбэки не вызываются – вместо этого они будут вызваны при закрытии нового стека (явно или неявно в конце оператора [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with)).361362Например, группу файлов можно открыть как операцию «всё или ничего» следующим образом:363364```python365with ExitStack() as stack:366 files = [stack.enter_context(open(fname)) for fname in filenames]367 # Сохраните метод close, но пока не вызывайте его.368 close_files = stack.pop_all().close369 # Если открытие любого файла завершается неудачей, все ранее открытые файлы будут370 # закрыты автоматически. Если все файлы открыты успешно,371 # они останутся открытыми даже после завершения оператора with.372 # close_files() затем можно вызвать явно, чтобы закрыть их все.373```374375#### `close()`376377Немедленно разворачивает стек колбэков, вызывая колбэки в порядке, обратном регистрации. Для любых зарегистрированных менеджеров контекста и колбэков выхода переданные аргументы будут указывать, что исключения не произошло.378379#### `class contextlib.AsyncExitStack`380381[Асинхронный менеджер контекста](https://python-all.ru/3.7/reference/datamodel.html#async-context-managers), аналогичный [`ExitStack`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ExitStack), который поддерживает объединение как синхронных, так и асинхронных менеджеров контекста, а также имеет корутины для логики очистки.382383Метод `close()` не реализован, [`aclose()`](https://python-all.ru/3.7/library/contextlib.html#contextlib.AsyncExitStack.aclose) должен использоваться вместо.384385#### `enter_async_context(cm)`386387Аналогично `enter_context()`, но ожидает асинхронный менеджер контекста.388389#### `push_async_exit(exit)`390391Аналогично `push()`, но ожидает либо асинхронный менеджер контекста, либо корутинную функцию.392393#### `push_async_callback(callback, *args, **kwds)`394395Аналогично `callback()`, но ожидает корутинную функцию.396397#### `aclose()`398399Аналогично `close()`, но корректно обрабатывает ожидаемые объекты.400401Продолжая пример для [`asynccontextmanager()`](https://python-all.ru/3.7/library/contextlib.html#contextlib.asynccontextmanager):402403```python404async with AsyncExitStack() as stack:405 connections = [await stack.enter_async_context(get_connection())406 for i in range(5)]407 # Все открытые соединения будут автоматически освобождены в конце408 # оператора async with, даже если попытки открыть соединение409 # далее в списке вызывают исключение.410```411412Добавлено в версии 3.7.413414## Примеры и рецепты415416В этом разделе описаны некоторые примеры и рецепты для эффективного использования инструментов, предоставляемых [`contextlib`](https://python-all.ru/3.7/library/contextlib.html#module-contextlib).417418### Поддержка переменного числа менеджеров контекста419420Основной вариант использования [`ExitStack`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ExitStack) – это тот, что описан в документации класса: поддержка переменного числа менеджеров контекста и других операций очистки в одном операторе [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with). Переменность может быть обусловлена тем, что количество необходимых менеджеров контекста определяется пользовательским вводом (например, открытие указанной пользователем коллекции файлов), или тем, что некоторые менеджеры контекста являются необязательными:421422```python423with ExitStack() as stack:424 for resource in resources:425 stack.enter_context(resource)426 if need_special_resource():427 special = acquire_special_resource()428 stack.callback(release_special_resource, special)429 # Выполните операции, использующие полученные ресурсы430```431432Как показано, [`ExitStack`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ExitStack) также значительно упрощает использование операторов [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with) для управления произвольными ресурсами, которые изначально не поддерживают протокол управления контекстом.433434### Перехват исключений из методов `__enter__`435436Иногда бывает желательно перехватывать исключения из реализации метода `__enter__`, *не* перехватывая случайно исключения из тела оператора [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with) или метода `__exit__` менеджера контекста. Используя [`ExitStack`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ExitStack), можно немного разделить шаги протокола управления контекстом, чтобы это стало возможным:437438```python439stack = ExitStack()440try:441 x = stack.enter_context(cm)442except Exception:443 # обработать исключение __enter__444else:445 with stack:446 # Обработка нормального случая447```448449Необходимость в этом, скорее всего, указывает на то, что базовый API должен предоставлять прямой интерфейс управления ресурсами для использования с операторами [`try`](https://python-all.ru/3.7/reference/compound_stmts.html#try)/[`except`](https://python-all.ru/3.7/reference/compound_stmts.html#except)/[`finally`](https://python-all.ru/3.7/reference/compound_stmts.html#finally), но не все API хорошо спроектированы в этом отношении. Когда менеджер контекста является единственным предоставляемым API управления ресурсами, тогда [`ExitStack`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ExitStack) может упростить обработку различных ситуаций, которые невозможно обработать напрямую в операторе [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with).450451### Очистка в реализации `__enter__`452453Как отмечено в документации [`ExitStack.push()`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ExitStack.push), этот метод может быть полезен для очистки уже выделенного ресурса, если последующие шаги в реализации [`__enter__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__enter__) завершатся неудачей.454455Вот пример реализации этого для менеджера контекста, который принимает функции захвата и освобождения ресурса, а также опциональную функцию проверки, и отображает их на протокол управления контекстом:456457```python458from contextlib import contextmanager, AbstractContextManager, ExitStack459460class ResourceManager(AbstractContextManager):461462 def __init__(self, acquire_resource, release_resource, check_resource_ok=None):463 self.acquire_resource = acquire_resource464 self.release_resource = release_resource465 if check_resource_ok is None:466 def check_resource_ok(resource):467 return True468 self.check_resource_ok = check_resource_ok469470 @contextmanager471 def _cleanup_on_error(self):472 with ExitStack() as stack:473 stack.push(self)474 yield475 # Проверка прошла успешно и не вызвала исключение476 # Соответственно, мы хотим сохранить ресурс и передать его477 # обратно вызывающему коду478 stack.pop_all()479480 def __enter__(self):481 resource = self.acquire_resource()482 with self._cleanup_on_error():483 if not self.check_resource_ok(resource):484 msg = "Failed validation for {!r}"485 raise RuntimeError(msg.format(resource))486 return resource487488 def __exit__(self, *exc_details):489 # Нам не нужно дублировать логику освобождения ресурсов490 self.release_resource()491```492493### Замена любого использования `try-finally` и флаговых переменных494495Иногда встречается шаблон, в котором используется инструкция `try-finally` с переменной-флагом, указывающей, должно ли выполняться тело предложения `finally`. В простейшей форме (если это нельзя обработать простым использованием предложения `except`) это выглядит примерно так:496497```python498cleanup_needed = True499try:500 result = perform_operation()501 if result:502 cleanup_needed = False503finally:504 if cleanup_needed:505 cleanup_resources()506```507508Как и в любом коде, основанном на инструкции `try`, это может вызвать проблемы при разработке и рецензировании, поскольку код настройки и код очистки могут оказаться разделенными произвольно длинными участками кода.509510[`ExitStack`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ExitStack) позволяет вместо этого зарегистрировать колбэк для выполнения в конце инструкции `with`, а затем позже решить пропустить выполнение этого колбэка:511512```python513from contextlib import ExitStack514515with ExitStack() as stack:516 stack.callback(cleanup_resources)517 result = perform_operation()518 if result:519 stack.pop_all()520```521522Это позволяет заранее явно указать предполагаемое поведение очистки, вместо того чтобы требовать отдельную переменную-флаг.523524Если конкретное приложение часто использует этот шаблон, его можно упростить еще больше с помощью небольшого вспомогательного класса:525526```python527from contextlib import ExitStack528529class Callback(ExitStack):530 def __init__(self, callback, *args, **kwds):531 super(Callback, self).__init__()532 self.callback(callback, *args, **kwds)533534 def cancel(self):535 self.pop_all()536537with Callback(cleanup_resources) as cb:538 result = perform_operation()539 if result:540 cb.cancel()541```542543Если очистка ресурсов уже не аккуратно оформлена в виде отдельной функции, то все равно можно использовать форму декоратора [`ExitStack.callback()`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ExitStack.callback), чтобы объявить очистку ресурсов заранее:544545```python546from contextlib import ExitStack547548with ExitStack() as stack:549 @stack.callback550 def cleanup_resources():551 ...552 result = perform_operation()553 if result:554 stack.pop_all()555```556557Из-за того, как работает протокол декоратора, функция обратного вызова, объявленная таким образом, не может принимать никаких параметров. Вместо этого любые ресурсы, которые необходимо освободить, должны быть доступны как переменные замыкания.558559### Использование менеджера контекста в качестве декоратора функции560561[`ContextDecorator`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ContextDecorator) позволяет использовать менеджер контекста как в обычной инструкции `with`, так и в качестве декоратора функции.562563Например, иногда полезно оборачивать функции или группы инструкций в логгер, который может отслеживать время входа и время выхода. Вместо написания как декоратора функции, так и менеджера контекста для этой задачи, наследование от [`ContextDecorator`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ContextDecorator) предоставляет обе возможности в одном определении:564565```python566from contextlib import ContextDecorator567import logging568569logging.basicConfig(level=logging.INFO)570571class track_entry_and_exit(ContextDecorator):572 def __init__(self, name):573 self.name = name574575 def __enter__(self):576 logging.info('Entering: %s', self.name)577578 def __exit__(self, exc_type, exc, exc_tb):579 logging.info('Exiting: %s', self.name)580```581582Экземпляры этого класса можно использовать как менеджер контекста:583584```python585with track_entry_and_exit('widget loader'):586 print('Some time consuming activity goes here')587 load_widget()588```589590А также как декоратор функции:591592```python593@track_entry_and_exit('widget loader')594def activity():595 print('Some time consuming activity goes here')596 load_widget()597```598599Обратите внимание, что есть одно дополнительное ограничение при использовании менеджеров контекста в качестве декораторов функций: невозможно получить доступ к возвращаемому значению [`__enter__()`](https://python-all.ru/3.7/reference/datamodel.html#object.__enter__). Если это значение необходимо, то все равно нужно использовать явную инструкцию `with`.600601> **См. также**602>603> **[**PEP 343**](https://python-all.ru/3.7/library/contextlib.html) – Оператор «with»**604>605> Спецификация, предыстория и примеры для инструкции [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with) в Python.606607## Одноразовые, многократно используемые и реентерабельные менеджеры контекста608609Большинство менеджеров контекста написаны таким образом, что их можно эффективно использовать только один раз в инструкции [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with). Эти одноразовые менеджеры контекста должны создаваться заново каждый раз при использовании – попытка использовать их второй раз вызовет исключение или иным образом приведет к некорректной работе.610611Это распространенное ограничение означает, что обычно рекомендуется создавать менеджеры контекста непосредственно в заголовке инструкции [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with), где они используются (как показано во всех примерах использования выше).612613Файлы являются примером эффективно одноразовых менеджеров контекста, поскольку первая инструкция [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with) закроет файл, предотвращая любые дальнейшие операции ввода-вывода с использованием этого объекта файла.614615Менеджеры контекста, созданные с помощью [`contextmanager()`](https://python-all.ru/3.7/library/contextlib.html#contextlib.contextmanager), также являются одноразовыми менеджерами контекста и будут сообщать об ошибке, если базовый генератор не сможет выполнить yield при попытке использовать их второй раз:616617```python618>>> from contextlib import contextmanager619>>> @contextmanager620... def singleuse():621... print("Before")622... yield623... print("After")624...625>>> cm = singleuse()626>>> with cm:627... pass628...629Before630After631>>> with cm:632... pass633...634Traceback (most recent call last):635 ...636RuntimeError: generator didn't yield637```638639### Реентерабельные менеджеры контекста640641Более сложные менеджеры контекста могут быть «реентерабельными». Такие менеджеры контекста можно использовать не только в нескольких инструкциях [`with`](https://python-all.ru/3.7/reference/compound_stmts.html#with), но также *внутри* инструкции `with`, которая уже использует тот же менеджер контекста.642643[`threading.RLock`](https://python-all.ru/3.7/library/threading.html#threading.RLock) – это пример реентерабельного контекстного менеджера, как и [`suppress()`](https://python-all.ru/3.7/library/contextlib.html#contextlib.suppress) и [`redirect_stdout()`](https://python-all.ru/3.7/library/contextlib.html#contextlib.redirect_stdout). Вот очень простой пример реентерабельного использования:644645```python646>>> from contextlib import redirect_stdout647>>> from io import StringIO648>>> stream = StringIO()649>>> write_to_stream = redirect_stdout(stream)650>>> with write_to_stream:651... print("This is written to the stream rather than stdout")652... with write_to_stream:653... print("This is also written to the stream")654...655>>> print("This is written directly to stdout")656This is written directly to stdout657>>> print(stream.getvalue())658This is written to the stream rather than stdout659This is also written to the stream660```661662Реальные примеры реентерабельности, скорее всего, будут включать несколько функций, вызывающих друг друга, и, следовательно, будут гораздо сложнее этого примера.663664Также обратите внимание, что быть реентерабельным – это *не* то же самое, что быть потокобезопасным. [`redirect_stdout()`](https://python-all.ru/3.7/library/contextlib.html#contextlib.redirect_stdout), например, определенно не является потокобезопасным, так как он вносит глобальное изменение в состояние системы, привязывая [`sys.stdout`](https://python-all.ru/3.7/library/sys.html#sys.stdout) к другому потоку данных.665666### Многократно используемые менеджеры контекста667668Отличающиеся от одноразовых и реентерабельных менеджеров контекста – «многократно используемые» менеджеры контекста (или, если быть совсем точным, «многократно используемые, но не реентерабельные» менеджеры контекста, поскольку реентерабельные менеджеры контекста также являются многократно используемыми). Эти менеджеры контекста поддерживают многократное использование, но завершатся ошибкой (или иным образом будут работать некорректно), если конкретный экземпляр менеджера контекста уже был использован во внешней инструкции with.669670[`threading.Lock`](https://python-all.ru/3.7/library/threading.html#threading.Lock) является примером многократно используемого, но не реентерабельного менеджера контекста (для реентерабельной блокировки необходимо использовать [`threading.RLock`](https://python-all.ru/3.7/library/threading.html#threading.RLock) вместо этого).671672Другим примером многократно используемого, но не реентерабельного менеджера контекста является [`ExitStack`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ExitStack), так как он вызывает *все* зарегистрированные в данный момент колбэки при выходе из любой инструкции with, независимо от того, где эти колбэки были добавлены:673674```python675>>> from contextlib import ExitStack676>>> stack = ExitStack()677>>> with stack:678... stack.callback(print, "Callback: from first context")679... print("Leaving first context")680...681Leaving first context682Callback: from first context683>>> with stack:684... stack.callback(print, "Callback: from second context")685... print("Leaving second context")686...687Leaving second context688Callback: from second context689>>> with stack:690... stack.callback(print, "Callback: from outer context")691... with stack:692... stack.callback(print, "Callback: from inner context")693... print("Leaving inner context")694... print("Leaving outer context")695...696Leaving inner context697Callback: from inner context698Callback: from outer context699Leaving outer context700```701702Как показывает вывод примера, повторное использование одного объекта стека в нескольких инструкциях with работает корректно, но попытка вложить их приведет к очистке стека в конце самой внутренней инструкции with, что вряд ли является желательным поведением.703704Использование отдельных экземпляров [`ExitStack`](https://python-all.ru/3.7/library/contextlib.html#contextlib.ExitStack) вместо повторного использования одного экземпляра позволяет избежать этой проблемы:705706```python707>>> from contextlib import ExitStack708>>> with ExitStack() as outer_stack:709... outer_stack.callback(print, "Callback: from outer context")710... with ExitStack() as inner_stack:711... inner_stack.callback(print, "Callback: from inner context")712... print("Leaving inner context")713... print("Leaving outer context")714...715Leaving inner context716Callback: from inner context717Leaving outer context718Callback: from outer context719```720