Документация Python неофициальный перевод

contextlib.md

843 строк · 56.3 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.10/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.10/Lib/contextlib.py)1011---1213Этот модуль предоставляет утилиты для типовых задач, связанных с оператором [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with). Для получения дополнительной информации см. также [Типы менеджеров контекста](https://python-all.ru/3.10/library/stdtypes.html#typecontextmanager) и [Менеджеры контекста оператора with](https://python-all.ru/3.10/reference/datamodel.html#context-managers).1415## Утилиты1617Предоставляемые функции и классы:1819#### `class contextlib.AbstractContextManager`2021[абстрактный базовый класс](https://python-all.ru/3.10/glossary.html#term-abstract-base-class) для классов, реализующих [`object.__enter__()`](https://python-all.ru/3.10/reference/datamodel.html#object.__enter__) и [`object.__exit__()`](https://python-all.ru/3.10/reference/datamodel.html#object.__exit__). Предоставляется реализация по умолчанию для [`object.__enter__()`](https://python-all.ru/3.10/reference/datamodel.html#object.__enter__), которая возвращает `self`, а [`object.__exit__()`](https://python-all.ru/3.10/reference/datamodel.html#object.__exit__) – это абстрактный метод, который по умолчанию возвращает `None`. См. также определение [Типов менеджеров контекста](https://python-all.ru/3.10/library/stdtypes.html#typecontextmanager).2223Новое в версии 3.6.2425#### `class contextlib.AbstractAsyncContextManager`2627[абстрактный базовый класс](https://python-all.ru/3.10/glossary.html#term-abstract-base-class) для классов, реализующих [`object.__aenter__()`](https://python-all.ru/3.10/reference/datamodel.html#object.__aenter__) и [`object.__aexit__()`](https://python-all.ru/3.10/reference/datamodel.html#object.__aexit__). Предоставляется реализация по умолчанию для [`object.__aenter__()`](https://python-all.ru/3.10/reference/datamodel.html#object.__aenter__), которая возвращает `self`, а [`object.__aexit__()`](https://python-all.ru/3.10/reference/datamodel.html#object.__aexit__) – это абстрактный метод, который по умолчанию возвращает `None`. См. также определение [Асинхронных менеджеров контекста](https://python-all.ru/3.10/reference/datamodel.html#async-context-managers).2829Добавлено в версии 3.7.3031#### `@contextlib.contextmanager`3233Эта функция – [декоратор](https://python-all.ru/3.10/glossary.html#term-decorator), который можно использовать для определения фабричной функции для контекстных менеджеров оператора [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with), без необходимости создавать класс или отдельные методы `__enter__()` и `__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)51```5253Затем функцию можно использовать следующим образом:5455```python56>>> with managed_resource(timeout=3600) as resource:57...     # Ресурс освобождается в конце этого блока,58...     # даже если код в блоке вызывает исключение59```6061Декорируемая функция при вызове должна возвращать [генератор](https://python-all.ru/3.10/glossary.html#term-generator)-итератор. Этот итератор должен возвращать ровно одно значение, которое будет связано с целями в предложении `as` оператора [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with), если таковые имеются.6263В момент, где генератор делает yield, выполняется блок, вложенный в оператор [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with). Затем генератор возобновляется после выхода из блока. Если в блоке возникает необработанное исключение, оно повторно возбуждается внутри генератора в точке, где произошел yield. Таким образом, можно использовать оператор [`try`](https://python-all.ru/3.10/reference/compound_stmts.html#try)…[`except`](https://python-all.ru/3.10/reference/compound_stmts.html#except)…[`finally`](https://python-all.ru/3.10/reference/compound_stmts.html#finally) для перехвата ошибки (если есть) или для выполнения очистки. Если исключение перехватывается только для того, чтобы зарегистрировать его или выполнить какое-либо действие (а не подавить полностью), генератор должен повторно возбудить это исключение. В противном случае контекстный менеджер генератора сообщит оператору `with`, что исключение обработано, и выполнение продолжится с оператора, следующего сразу за оператором `with`.6465[`contextmanager()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.contextmanager) использует [`ContextDecorator`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ContextDecorator), поэтому создаваемые им контекстные менеджеры могут использоваться как в качестве декораторов, так и в операторах [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with). При использовании в качестве декоратора для каждого вызова функции неявно создается новый экземпляр генератора (это позволяет в противном случае «одноразовым» контекстным менеджерам, созданным с помощью [`contextmanager()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.contextmanager), соответствовать требованию, согласно которому контекстные менеджеры должны поддерживать многократные вызовы, чтобы их можно было использовать в качестве декораторов).6667Изменено в версии 3.2: Использование [`ContextDecorator`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ContextDecorator).6869#### `@contextlib.asynccontextmanager`7071> Аналог [`contextmanager()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.contextmanager), но создает [асинхронный контекстный менеджер](https://python-all.ru/3.10/reference/datamodel.html#async-context-managers).72>73> Эта функция – [декоратор](https://python-all.ru/3.10/glossary.html#term-decorator), который можно использовать для определения фабричной функции для асинхронных контекстных менеджеров оператора [`async with`](https://python-all.ru/3.10/reference/compound_stmts.html#async-with), без необходимости создавать класс или отдельные методы `__aenter__()` и `__aexit__()`. Она должна применяться к функции [асинхронного генератора](https://python-all.ru/3.10/glossary.html#term-asynchronous-generator).74>75> Простой пример:76>77> ```python78> from contextlib import asynccontextmanager79>80> @asynccontextmanager81> async def get_connection():82>     conn = await acquire_db_connection()83>     try:84>         yield conn85>     finally:86>         await release_db_connection(conn)87>88> async def get_all_users():89>     async with get_connection() as conn:90>         return conn.query('SELECT ...')91> ```92>93> Добавлено в версии 3.7.94>95> Контекстные менеджеры, определенные с помощью [`asynccontextmanager()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.asynccontextmanager), могут использоваться как в качестве декораторов, так и с операторами [`async with`](https://python-all.ru/3.10/reference/compound_stmts.html#async-with):96>97> ```python98> import time99> from contextlib import asynccontextmanager100>101> @asynccontextmanager102> async def timeit():103>     now = time.monotonic()104>     try:105>         yield106>     finally:107>         print(f'it took {time.monotonic() - now}s to run')108>109> @timeit()110> async def main():111>     # ... асинхронный код ...112> ```113>114> При использовании в качестве декоратора для каждого вызова функции неявно создается новый экземпляр генератора. Это позволяет в противном случае «одноразовым» контекстным менеджерам, созданным с помощью [`asynccontextmanager()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.asynccontextmanager), соответствовать требованию, согласно которому контекстные менеджеры должны поддерживать многократные вызовы, чтобы их можно было использовать в качестве декораторов.115116Изменено в версии 3.10: Асинхронные контекстные менеджеры, созданные с помощью [`asynccontextmanager()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.asynccontextmanager), могут использоваться в качестве декораторов.117118#### `contextlib.closing(thing)`119120Возвращает контекстный менеджер, который закрывает *thing* после завершения блока. По сути эквивалентно следующему:121122```python123from contextlib import contextmanager124125@contextmanager126def closing(thing):127    try:128        yield thing129    finally:130        thing.close()131```132133И позволяет писать код следующим образом:134135```python136from contextlib import closing137from urllib.request import urlopen138139with closing(urlopen('https://www.python.org')) as page:140    for line in page:141        print(line)142```143144без необходимости явно закрывать `page`. Даже если произойдет ошибка, `page.close()` будет вызван при выходе из блока [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with).145146#### `contextlib.aclosing(thing)`147148Возвращает асинхронный контекстный менеджер, который вызывает метод `aclose()` объекта *thing* после завершения блока. По сути эквивалентно следующему:149150```python151from contextlib import asynccontextmanager152153@asynccontextmanager154async def aclosing(thing):155    try:156        yield thing157    finally:158        await thing.aclose()159```160161Примечательно, что `aclosing()` поддерживает детерминированную очистку асинхронных генераторов, когда они завершаются досрочно из-за [`break`](https://python-all.ru/3.10/reference/simple_stmts.html#break) или исключения. Например:162163```python164from contextlib import aclosing165166async with aclosing(my_generator()) as values:167    async for value in values:168        if value == 42:169            break170```171172Этот шаблон гарантирует, что асинхронный код выхода генератора выполняется в том же контексте, что и его итерации (чтобы исключения и контекстные переменные работали как ожидается, а код выхода не выполнялся после завершения времени жизни задачи, от которой он зависит).173174Новое в версии 3.10.175176#### `contextlib.nullcontext(enter_result=None)`177178Возвращает менеджер контекста, который возвращает *enter\_result* из `__enter__`, но в остальном ничего не делает. Предназначен для использования в качестве замены опционального менеджера контекста, например:179180```python181def myfunction(arg, ignore_exceptions=False):182    if ignore_exceptions:183        # Используйте suppress, чтобы игнорировать все исключения.184        cm = contextlib.suppress(Exception)185    else:186        # Не игнорируйте никакие исключения, cm не оказывает эффекта.187        cm = contextlib.nullcontext()188    with cm:189        # Выполнить действие190```191192Пример использования *enter\_result*:193194```python195def process_file(file_or_path):196    if isinstance(file_or_path, str):197        # Если строка, открыть файл198        cm = open(file_or_path)199    else:200        # Вызывающий отвечает за закрытие файла201        cm = nullcontext(file_or_path)202203    with cm as file:204        # Выполнить обработку файла205```206207Его также можно использовать в качестве замены [асинхронных менеджеров контекста](https://python-all.ru/3.10/reference/datamodel.html#async-context-managers):208209```python210async def send_http(session=None):211    if not session:212        # Если нет http-сессии, создать её с помощью aiohttp213        cm = aiohttp.ClientSession()214    else:215        # Вызывающий отвечает за закрытие сессии216        cm = nullcontext(session)217218    async with cm as session:219        # Отправить http-запросы с помощью сессии220```221222Добавлено в версии 3.7.223224Изменено в версии 3.10: добавлена поддержка [асинхронного менеджера контекста](https://python-all.ru/3.10/glossary.html#term-asynchronous-context-manager).225226#### `contextlib.suppress(*exceptions)`227228Возвращает менеджер контекста, который подавляет любые из указанных исключений, если они возникают в теле оператора `with`, а затем возобновляет выполнение с первого оператора после завершения оператора `with`.229230Как и любой другой механизм, полностью подавляющий исключения, этот менеджер контекста следует использовать только для обработки очень специфических ошибок, когда молчаливое продолжение выполнения программы является правильным решением.231232Например:233234```python235from contextlib import suppress236237with suppress(FileNotFoundError):238    os.remove('somefile.tmp')239240with suppress(FileNotFoundError):241    os.remove('someotherfile.tmp')242```243244Этот код эквивалентен:245246```python247try:248    os.remove('somefile.tmp')249except FileNotFoundError:250    pass251252try:253    os.remove('someotherfile.tmp')254except FileNotFoundError:255    pass256```257258Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.10/library/contextlib.html#reentrant-cms).259260Новое в версии 3.4.261262#### `contextlib.redirect_stdout(new_target)`263264Менеджер контекста для временного перенаправления [`sys.stdout`](https://python-all.ru/3.10/library/sys.html#sys.stdout) в другой файл или файлоподобный объект.265266Этот инструмент добавляет гибкость существующим функциям или классам, чей вывод жестко привязан к stdout.267268Например, вывод [`help()`](https://python-all.ru/3.10/library/functions.html#help) обычно отправляется в *sys.stdout*. Этот вывод можно захватить в строку, перенаправив его на объект [`io.StringIO`](https://python-all.ru/3.10/library/io.html#io.StringIO). Заменяющий поток данных возвращается из метода `__enter__` и поэтому доступен как цель оператора [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with):269270```python271with redirect_stdout(io.StringIO()) as f:272    help(pow)273s = f.getvalue()274```275276Чтобы отправить вывод [`help()`](https://python-all.ru/3.10/library/functions.html#help) в файл на диске, перенаправьте вывод в обычный файл:277278```python279with open('help.txt', 'w') as f:280    with redirect_stdout(f):281        help(pow)282```283284Чтобы отправить вывод [`help()`](https://python-all.ru/3.10/library/functions.html#help) в *sys.stderr*:285286```python287with redirect_stdout(sys.stderr):288    help(pow)289```290291Обратите внимание, что глобальный побочный эффект на [`sys.stdout`](https://python-all.ru/3.10/library/sys.html#sys.stdout) означает, что этот менеджер контекста не подходит для использования в библиотечном коде и большинстве многопоточных приложений. Он также не влияет на вывод подпроцессов. Тем не менее, это всё ещё полезный подход для многих вспомогательных скриптов.292293Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.10/library/contextlib.html#reentrant-cms).294295Новое в версии 3.4.296297#### `contextlib.redirect_stderr(new_target)`298299Аналогично [`redirect_stdout()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.redirect_stdout), но перенаправляет [`sys.stderr`](https://python-all.ru/3.10/library/sys.html#sys.stderr) в другой файл или файлоподобный объект.300301Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.10/library/contextlib.html#reentrant-cms).302303Новое в версии 3.5.304305#### `class contextlib.ContextDecorator`306307Базовый класс, который позволяет менеджеру контекста также использоваться в качестве декоратора.308309Менеджеры контекста, наследующие от `ContextDecorator`, должны обычным образом реализовывать `__enter__` и `__exit__`. `__exit__` сохраняет необязательную обработку исключений даже при использовании в качестве декоратора.310311`ContextDecorator` используется [`contextmanager()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.contextmanager), поэтому эта функциональность доступна автоматически.312313Пример `ContextDecorator`:314315```python316from contextlib import ContextDecorator317318class mycontext(ContextDecorator):319    def __enter__(self):320        print('Starting')321        return self322323    def __exit__(self, *exc):324        print('Finishing')325        return False326```327328Затем класс можно использовать следующим образом:329330```python331>>> @mycontext()332... def function():333...     print('The bit in the middle')334...335>>> function()336Starting337The bit in the middle338Finishing339340>>> with mycontext():341...     print('The bit in the middle')342...343Starting344The bit in the middle345Finishing346```347348Это изменение – всего лишь синтаксический сахар для любой конструкции следующего вида:349350```python351def f():352    with cm():353        # Выполнить действия354```355356`ContextDecorator` позволяет вместо этого писать:357358```python359@cm()360def f():361    # Выполнить действия362```363364Становится ясно, что `cm` применяется ко всей функции, а не только к её части (и экономия уровня отступа тоже приятна).365366Существующие менеджеры контекста, уже имеющие базовый класс, можно расширить, используя `ContextDecorator` в качестве примеси (mixin):367368```python369from contextlib import ContextDecorator370371class mycontext(ContextBaseClass, ContextDecorator):372    def __enter__(self):373        return self374375    def __exit__(self, *exc):376        return False377```378379> **Примечание**380>381> Поскольку декорированная функция должна допускать многократный вызов, нижележащий менеджер контекста должен поддерживать использование в нескольких операторах [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with). Если это не так, следует использовать исходную конструкцию с явным оператором `with` внутри функции.382383Новое в версии 3.2.384385#### `class contextlib.AsyncContextDecorator`386387Аналогично [`ContextDecorator`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ContextDecorator), но только для асинхронных функций.388389Пример `AsyncContextDecorator`:390391```python392from asyncio import run393from contextlib import AsyncContextDecorator394395class mycontext(AsyncContextDecorator):396    async def __aenter__(self):397        print('Starting')398        return self399400    async def __aexit__(self, *exc):401        print('Finishing')402        return False403```404405Затем класс можно использовать следующим образом:406407```python408>>> @mycontext()409... async def function():410...     print('The bit in the middle')411...412>>> run(function())413Starting414The bit in the middle415Finishing416417>>> async def function():418...    async with mycontext():419...         print('The bit in the middle')420...421>>> run(function())422Starting423The bit in the middle424Finishing425```426427Новое в версии 3.10.428429#### `class contextlib.ExitStack`430431Менеджер контекста, предназначенный для упрощения программного комбинирования других менеджеров контекста и функций очистки, особенно тех, которые являются необязательными или определяются входными данными.432433Например, набор файлов можно легко обработать в одном операторе with следующим образом:434435```python436with ExitStack() as stack:437    files = [stack.enter_context(open(fname)) for fname in filenames]438    # Все открытые файлы будут автоматически закрыты в конце439    # оператор with, даже если попытки открыть файлы, идущие далее440    # в списке, вызывают исключение441```442443Метод `__enter__()` возвращает экземпляр [`ExitStack`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ExitStack) и не выполняет никаких дополнительных операций.444445Каждый экземпляр поддерживает стек зарегистрированных колбэков, которые вызываются в обратном порядке при закрытии экземпляра (явно или неявно в конце оператора [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with)). Обратите внимание, что колбэки *не* вызываются неявно при сборке мусора экземпляра стека контекста.446447Такая стековая модель используется, чтобы менеджеры контекста, получающие свои ресурсы в методе `__init__` (например, файловые объекты), могли обрабатываться корректно.448449Поскольку зарегистрированные колбэки вызываются в порядке, обратном регистрации, в итоге это ведёт себя так, как если бы с зарегистрированным набором колбэков использовалось несколько вложенных операторов [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with). Это распространяется даже на обработку исключений: если внутренний колбэк подавляет или заменяет исключение, то внешним колбэкам будут переданы аргументы, основанные на этом обновлённом состоянии.450451Это относительно низкоуровневый API, который заботится о деталях корректной размотки стека колбэков выхода. Он предоставляет подходящую основу для менеджеров контекста более высокого уровня, которые манипулируют стеком выхода специфичным для приложения образом.452453Новое в версии 3.3.454455#### `enter_context(cm)`456457Входит в новый менеджер контекста и добавляет его метод `__exit__()` в стек колбэков. Возвращаемое значение – результат собственного метода `__enter__()` менеджера контекста.458459Эти менеджеры контекста могут подавлять исключения точно так же, как они это делали бы при непосредственном использовании в операторе [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with).460461#### `push(exit)`462463Добавляет метод `__exit__()` менеджера контекста в стек колбэков.464465Поскольку `__enter__` *не* вызывается, этот метод можно использовать для покрытия части реализации `__enter__()` собственным методом `__exit__()` менеджера контекста.466467Если передан объект, не являющийся менеджером контекста, этот метод считает его колбэком с той же сигнатурой, что и метод `__exit__()` менеджера контекста, и добавляет его напрямую в стек колбэков.468469Возвращая истинные значения, эти колбэки могут подавлять исключения так же, как это делают методы `__exit__()` менеджеров контекста.470471Переданный объект возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.472473#### `callback(callback, /, *args, **kwds)`474475Принимает произвольную функцию-колбэк и аргументы и добавляет её в стек колбэков.476477В отличие от других методов, колбэки, добавленные таким образом, не могут подавлять исключения (поскольку им никогда не передаются сведения об исключении).478479Переданный колбэк возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.480481#### `pop_all()`482483Перемещает стек колбэков в новый экземпляр [`ExitStack`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ExitStack) и возвращает его. При этой операции колбэки не вызываются – вместо этого они будут вызваны при закрытии нового стека (явно или неявно в конце оператора [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with)).484485Например, группу файлов можно открыть как операцию «всё или ничего» следующим образом:486487```python488with ExitStack() as stack:489    files = [stack.enter_context(open(fname)) for fname in filenames]490    # Сохраните метод close, но пока не вызывайте его.491    close_files = stack.pop_all().close492    # Если открытие любого файла завершается неудачей, все ранее открытые файлы будут493    # закрыты автоматически. Если все файлы открыты успешно,494    # они останутся открытыми даже после завершения оператора with.495    # close_files() затем можно вызвать явно, чтобы закрыть их все.496```497498#### `close()`499500Немедленно разворачивает стек колбэков, вызывая колбэки в порядке, обратном регистрации. Для любых зарегистрированных менеджеров контекста и колбэков выхода переданные аргументы будут указывать, что исключения не произошло.501502#### `class contextlib.AsyncExitStack`503504[Асинхронный менеджер контекста](https://python-all.ru/3.10/reference/datamodel.html#async-context-managers), аналогичный [`ExitStack`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ExitStack), который поддерживает объединение как синхронных, так и асинхронных менеджеров контекста, а также имеет корутины для логики очистки.505506Метод `close()` не реализован, [`aclose()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.AsyncExitStack.aclose) должен использоваться вместо.507508#### `coroutine enter_async_context(cm)`509510Аналогично `enter_context()`, но ожидает асинхронный менеджер контекста.511512#### `push_async_exit(exit)`513514Аналогично `push()`, но ожидает либо асинхронный менеджер контекста, либо корутинную функцию.515516#### `push_async_callback(callback, /, *args, **kwds)`517518Аналогично `callback()`, но ожидает корутинную функцию.519520#### `coroutine aclose()`521522Аналогично `close()`, но корректно обрабатывает ожидаемые объекты.523524Продолжая пример для [`asynccontextmanager()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.asynccontextmanager):525526```python527async with AsyncExitStack() as stack:528    connections = [await stack.enter_async_context(get_connection())529        for i in range(5)]530    # Все открытые соединения будут автоматически освобождены в конце531    # оператора async with, даже если попытки открыть соединение532    # далее в списке вызывают исключение.533```534535Добавлено в версии 3.7.536537## Примеры и рецепты538539В этом разделе описаны некоторые примеры и рецепты для эффективного использования инструментов, предоставляемых [`contextlib`](https://python-all.ru/3.10/library/contextlib.html#module-contextlib).540541### Поддержка переменного числа менеджеров контекста542543Основной вариант использования [`ExitStack`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ExitStack) – это тот, что описан в документации класса: поддержка переменного числа менеджеров контекста и других операций очистки в одном операторе [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with). Переменность может быть обусловлена тем, что количество необходимых менеджеров контекста определяется пользовательским вводом (например, открытие указанной пользователем коллекции файлов), или тем, что некоторые менеджеры контекста являются необязательными:544545```python546with ExitStack() as stack:547    for resource in resources:548        stack.enter_context(resource)549    if need_special_resource():550        special = acquire_special_resource()551        stack.callback(release_special_resource, special)552    # Выполните операции, использующие полученные ресурсы553```554555Как показано, [`ExitStack`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ExitStack) также значительно упрощает использование операторов [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with) для управления произвольными ресурсами, которые изначально не поддерживают протокол управления контекстом.556557### Перехват исключений из методов `__enter__`558559Иногда бывает желательно перехватывать исключения из реализации метода `__enter__`, *не* перехватывая случайно исключения из тела оператора [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with) или метода `__exit__` менеджера контекста. Используя [`ExitStack`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ExitStack), можно немного разделить шаги протокола управления контекстом, чтобы это стало возможным:560561```python562stack = ExitStack()563try:564    x = stack.enter_context(cm)565except Exception:566    # обработать исключение __enter__567else:568    with stack:569        # Обработка нормального случая570```571572Необходимость в этом, скорее всего, указывает на то, что базовый API должен предоставлять прямой интерфейс управления ресурсами для использования с операторами [`try`](https://python-all.ru/3.10/reference/compound_stmts.html#try)/[`except`](https://python-all.ru/3.10/reference/compound_stmts.html#except)/[`finally`](https://python-all.ru/3.10/reference/compound_stmts.html#finally), но не все API хорошо спроектированы в этом отношении. Когда менеджер контекста является единственным предоставляемым API управления ресурсами, тогда [`ExitStack`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ExitStack) может упростить обработку различных ситуаций, которые невозможно обработать напрямую в операторе [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with).573574### Очистка в реализации `__enter__`575576Как отмечено в документации [`ExitStack.push()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ExitStack.push), этот метод может быть полезен для очистки уже выделенного ресурса, если последующие шаги в реализации `__enter__()` завершатся неудачей.577578Вот пример реализации этого для менеджера контекста, который принимает функции захвата и освобождения ресурса, а также опциональную функцию проверки, и отображает их на протокол управления контекстом:579580```python581from contextlib import contextmanager, AbstractContextManager, ExitStack582583class ResourceManager(AbstractContextManager):584585    def __init__(self, acquire_resource, release_resource, check_resource_ok=None):586        self.acquire_resource = acquire_resource587        self.release_resource = release_resource588        if check_resource_ok is None:589            def check_resource_ok(resource):590                return True591        self.check_resource_ok = check_resource_ok592593    @contextmanager594    def _cleanup_on_error(self):595        with ExitStack() as stack:596            stack.push(self)597            yield598            # Проверка прошла успешно и не вызвала исключение599            # Соответственно, мы хотим сохранить ресурс и передать его600            # обратно вызывающему коду601            stack.pop_all()602603    def __enter__(self):604        resource = self.acquire_resource()605        with self._cleanup_on_error():606            if not self.check_resource_ok(resource):607                msg = "Failed validation for {!r}"608                raise RuntimeError(msg.format(resource))609        return resource610611    def __exit__(self, *exc_details):612        # Нам не нужно дублировать логику освобождения ресурсов613        self.release_resource()614```615616### Замена любого использования `try-finally` и флаговых переменных617618Иногда встречается шаблон, в котором используется инструкция `try-finally` с переменной-флагом, указывающей, должно ли выполняться тело предложения `finally`. В простейшей форме (если это нельзя обработать простым использованием предложения `except`) это выглядит примерно так:619620```python621cleanup_needed = True622try:623    result = perform_operation()624    if result:625        cleanup_needed = False626finally:627    if cleanup_needed:628        cleanup_resources()629```630631Как и в любом коде, основанном на инструкции `try`, это может вызвать проблемы при разработке и рецензировании, поскольку код настройки и код очистки могут оказаться разделенными произвольно длинными участками кода.632633[`ExitStack`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ExitStack) позволяет вместо этого зарегистрировать колбэк для выполнения в конце инструкции `with`, а затем позже решить пропустить выполнение этого колбэка:634635```python636from contextlib import ExitStack637638with ExitStack() as stack:639    stack.callback(cleanup_resources)640    result = perform_operation()641    if result:642        stack.pop_all()643```644645Это позволяет заранее явно указать предполагаемое поведение очистки, вместо того чтобы требовать отдельную переменную-флаг.646647Если конкретное приложение часто использует этот шаблон, его можно упростить еще больше с помощью небольшого вспомогательного класса:648649```python650from contextlib import ExitStack651652class Callback(ExitStack):653    def __init__(self, callback, /, *args, **kwds):654        super().__init__()655        self.callback(callback, *args, **kwds)656657    def cancel(self):658        self.pop_all()659660with Callback(cleanup_resources) as cb:661    result = perform_operation()662    if result:663        cb.cancel()664```665666Если очистка ресурсов уже не аккуратно оформлена в виде отдельной функции, то все равно можно использовать форму декоратора [`ExitStack.callback()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ExitStack.callback), чтобы объявить очистку ресурсов заранее:667668```python669from contextlib import ExitStack670671with ExitStack() as stack:672    @stack.callback673    def cleanup_resources():674        ...675    result = perform_operation()676    if result:677        stack.pop_all()678```679680Из-за того, как работает протокол декоратора, функция обратного вызова, объявленная таким образом, не может принимать никаких параметров. Вместо этого любые ресурсы, которые необходимо освободить, должны быть доступны как переменные замыкания.681682### Использование менеджера контекста в качестве декоратора функции683684[`ContextDecorator`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ContextDecorator) позволяет использовать менеджер контекста как в обычной инструкции `with`, так и в качестве декоратора функции.685686Например, иногда полезно оборачивать функции или группы инструкций в логгер, который может отслеживать время входа и время выхода. Вместо написания как декоратора функции, так и менеджера контекста для этой задачи, наследование от [`ContextDecorator`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ContextDecorator) предоставляет обе возможности в одном определении:687688```python689from contextlib import ContextDecorator690import logging691692logging.basicConfig(level=logging.INFO)693694class track_entry_and_exit(ContextDecorator):695    def __init__(self, name):696        self.name = name697698    def __enter__(self):699        logging.info('Entering: %s', self.name)700701    def __exit__(self, exc_type, exc, exc_tb):702        logging.info('Exiting: %s', self.name)703```704705Экземпляры этого класса можно использовать как менеджер контекста:706707```python708with track_entry_and_exit('widget loader'):709    print('Some time consuming activity goes here')710    load_widget()711```712713А также как декоратор функции:714715```python716@track_entry_and_exit('widget loader')717def activity():718    print('Some time consuming activity goes here')719    load_widget()720```721722Обратите внимание, что есть одно дополнительное ограничение при использовании менеджеров контекста в качестве декораторов функций: невозможно получить доступ к возвращаемому значению `__enter__()`. Если это значение необходимо, то все равно нужно использовать явную инструкцию `with`.723724> **См. также**725>726> **[**PEP 343**](https://python-all.ru/3.10/library/contextlib.html) – Оператор «with»**727>728> Спецификация, предыстория и примеры для инструкции [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with) в Python.729730## Одноразовые, многократно используемые и реентерабельные менеджеры контекста731732Большинство менеджеров контекста написаны таким образом, что их можно эффективно использовать только один раз в инструкции [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with). Эти одноразовые менеджеры контекста должны создаваться заново каждый раз при использовании – попытка использовать их второй раз вызовет исключение или иным образом приведет к некорректной работе.733734Это распространенное ограничение означает, что обычно рекомендуется создавать менеджеры контекста непосредственно в заголовке инструкции [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with), где они используются (как показано во всех примерах использования выше).735736Файлы являются примером эффективно одноразовых менеджеров контекста, поскольку первая инструкция [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with) закроет файл, предотвращая любые дальнейшие операции ввода-вывода с использованием этого объекта файла.737738Менеджеры контекста, созданные с помощью [`contextmanager()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.contextmanager), также являются одноразовыми менеджерами контекста и будут сообщать об ошибке, если базовый генератор не сможет выполнить yield при попытке использовать их второй раз:739740```python741>>> from contextlib import contextmanager742>>> @contextmanager743... def singleuse():744...     print("Before")745...     yield746...     print("After")747...748>>> cm = singleuse()749>>> with cm:750...     pass751...752Before753After754>>> with cm:755...     pass756...757Traceback (most recent call last):758    ...759RuntimeError: generator didn't yield760```761762### Реентерабельные менеджеры контекста763764Более сложные менеджеры контекста могут быть «реентерабельными». Такие менеджеры контекста можно использовать не только в нескольких инструкциях [`with`](https://python-all.ru/3.10/reference/compound_stmts.html#with), но также *внутри* инструкции `with`, которая уже использует тот же менеджер контекста.765766[`threading.RLock`](https://python-all.ru/3.10/library/threading.html#threading.RLock) – это пример реентерабельного контекстного менеджера, как и [`suppress()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.suppress) и [`redirect_stdout()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.redirect_stdout). Вот очень простой пример реентерабельного использования:767768```python769>>> from contextlib import redirect_stdout770>>> from io import StringIO771>>> stream = StringIO()772>>> write_to_stream = redirect_stdout(stream)773>>> with write_to_stream:774...     print("This is written to the stream rather than stdout")775...     with write_to_stream:776...         print("This is also written to the stream")777...778>>> print("This is written directly to stdout")779This is written directly to stdout780>>> print(stream.getvalue())781This is written to the stream rather than stdout782This is also written to the stream783```784785Реальные примеры реентерабельности, скорее всего, будут включать несколько функций, вызывающих друг друга, и, следовательно, будут гораздо сложнее этого примера.786787Также обратите внимание, что быть реентерабельным – это *не* то же самое, что быть потокобезопасным. [`redirect_stdout()`](https://python-all.ru/3.10/library/contextlib.html#contextlib.redirect_stdout), например, определенно не является потокобезопасным, так как он вносит глобальное изменение в состояние системы, привязывая [`sys.stdout`](https://python-all.ru/3.10/library/sys.html#sys.stdout) к другому потоку данных.788789### Многократно используемые менеджеры контекста790791Отличающиеся от одноразовых и реентерабельных менеджеров контекста – «многократно используемые» менеджеры контекста (или, если быть совсем точным, «многократно используемые, но не реентерабельные» менеджеры контекста, поскольку реентерабельные менеджеры контекста также являются многократно используемыми). Эти менеджеры контекста поддерживают многократное использование, но завершатся ошибкой (или иным образом будут работать некорректно), если конкретный экземпляр менеджера контекста уже был использован во внешней инструкции with.792793[`threading.Lock`](https://python-all.ru/3.10/library/threading.html#threading.Lock) является примером многократно используемого, но не реентерабельного менеджера контекста (для реентерабельной блокировки необходимо использовать [`threading.RLock`](https://python-all.ru/3.10/library/threading.html#threading.RLock) вместо этого).794795Другим примером многократно используемого, но не реентерабельного менеджера контекста является [`ExitStack`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ExitStack), так как он вызывает *все* зарегистрированные в данный момент колбэки при выходе из любой инструкции with, независимо от того, где эти колбэки были добавлены:796797```python798>>> from contextlib import ExitStack799>>> stack = ExitStack()800>>> with stack:801...     stack.callback(print, "Callback: from first context")802...     print("Leaving first context")803...804Leaving first context805Callback: from first context806>>> with stack:807...     stack.callback(print, "Callback: from second context")808...     print("Leaving second context")809...810Leaving second context811Callback: from second context812>>> with stack:813...     stack.callback(print, "Callback: from outer context")814...     with stack:815...         stack.callback(print, "Callback: from inner context")816...         print("Leaving inner context")817...     print("Leaving outer context")818...819Leaving inner context820Callback: from inner context821Callback: from outer context822Leaving outer context823```824825Как показывает вывод примера, повторное использование одного объекта стека в нескольких инструкциях with работает корректно, но попытка вложить их приведет к очистке стека в конце самой внутренней инструкции with, что вряд ли является желательным поведением.826827Использование отдельных экземпляров [`ExitStack`](https://python-all.ru/3.10/library/contextlib.html#contextlib.ExitStack) вместо повторного использования одного экземпляра позволяет избежать этой проблемы:828829```python830>>> from contextlib import ExitStack831>>> with ExitStack() as outer_stack:832...     outer_stack.callback(print, "Callback: from outer context")833...     with ExitStack() as inner_stack:834...         inner_stack.callback(print, "Callback: from inner context")835...         print("Leaving inner context")836...     print("Leaving outer context")837...838Leaving inner context839Callback: from inner context840Leaving outer context841Callback: from outer context842```843