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

contextlib.md

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

1> **Источник:** https://python-all.ru/3.16/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/main/Lib/contextlib.py)1011---1213Этот модуль предоставляет утилиты для типовых задач, связанных с оператором [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with). Для получения дополнительной информации см. также [Типы менеджеров контекста](https://python-all.ru/3.16/library/stdtypes.html#typecontextmanager) и [Менеджеры контекста оператора with](https://python-all.ru/3.16/reference/datamodel.html#context-managers).1415## Утилиты1617Предоставляемые функции и классы:1819#### `class contextlib.AbstractContextManager`2021[абстрактный базовый класс](https://python-all.ru/3.16/glossary.html#term-abstract-base-class) для классов, реализующих [`__enter__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__enter__) и [`__exit__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__exit__). Предоставляется реализация по умолчанию для `__enter__()`, которая возвращает `self`, а `__exit__()` – это абстрактный метод, который по умолчанию возвращает `None`. См. также определение [Типов менеджеров контекста](https://python-all.ru/3.16/library/stdtypes.html#typecontextmanager).2223Добавлено в версии 3.6.2425#### `class contextlib.AbstractAsyncContextManager`2627[абстрактный базовый класс](https://python-all.ru/3.16/glossary.html#term-abstract-base-class) для классов, реализующих [`__aenter__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__aenter__) и [`__aexit__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__aexit__). Предоставляется реализация по умолчанию для `__aenter__()`, которая возвращает `self`, а `__aexit__()` – это абстрактный метод, который по умолчанию возвращает `None`. См. также определение [Асинхронных менеджеров контекста](https://python-all.ru/3.16/reference/datamodel.html#async-context-managers).2829Добавлено в версии 3.7.3031#### `@contextlib.contextmanager`3233Эта функция – [декоратор](https://python-all.ru/3.16/glossary.html#term-decorator), который можно использовать для определения фабричной функции для контекстных менеджеров оператора [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with), без необходимости создавать класс или отдельные методы [`__enter__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__enter__) и [`__exit__()`](https://python-all.ru/3.16/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)51```5253Затем функцию можно использовать следующим образом:5455```python56>>> with managed_resource(timeout=3600) as resource:57...     # Ресурс освобождается в конце этого блока,58...     # даже если код в блоке вызывает исключение59```6061Декорируемая функция при вызове должна возвращать [генератор](https://python-all.ru/3.16/glossary.html#term-generator)-итератор. Этот итератор должен возвращать ровно одно значение, которое будет связано с целями в предложении `as` оператора [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with), если таковые имеются.6263В момент, где генератор делает yield, выполняется блок, вложенный в оператор [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with). Затем генератор возобновляется после выхода из блока. Если в блоке возникает необработанное исключение, оно повторно возбуждается внутри генератора в точке, где произошел yield. Таким образом, можно использовать оператор [`try`](https://python-all.ru/3.16/reference/compound_stmts.html#try)…[`except`](https://python-all.ru/3.16/reference/compound_stmts.html#except)…[`finally`](https://python-all.ru/3.16/reference/compound_stmts.html#finally) для перехвата ошибки (если есть) или для выполнения очистки. Если исключение перехватывается только для того, чтобы зарегистрировать его или выполнить какое-либо действие (а не подавить полностью), генератор должен повторно возбудить это исключение. В противном случае контекстный менеджер генератора сообщит оператору `with`, что исключение обработано, и выполнение продолжится с оператора, следующего сразу за оператором `with`.6465[`@contextmanager`](https://python-all.ru/3.16/library/contextlib.html#contextlib.contextmanager) использует [`ContextDecorator`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ContextDecorator), поэтому создаваемые им контекстные менеджеры могут использоваться как в качестве декораторов, так и в операторах [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with). При использовании в качестве декоратора для каждого вызова функции неявно создается новый экземпляр генератора (это позволяет в противном случае «одноразовым» контекстным менеджерам, созданным с помощью `@contextmanager`, соответствовать требованию, согласно которому контекстные менеджеры должны поддерживать многократные вызовы, чтобы их можно было использовать в качестве декораторов).6667Изменено в версии 3.2: Использование [`ContextDecorator`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ContextDecorator).6869#### `@contextlib.asynccontextmanager`7071Аналог [`@~contextlib.contextmanager`](https://python-all.ru/3.16/library/contextlib.html#contextlib.contextmanager), но создает [асинхронный контекстный менеджер](https://python-all.ru/3.16/reference/datamodel.html#async-context-managers).7273Эта функция – [декоратор](https://python-all.ru/3.16/glossary.html#term-decorator), который можно использовать для определения фабричной функции для асинхронных контекстных менеджеров оператора [`async with`](https://python-all.ru/3.16/reference/compound_stmts.html#async-with), без необходимости создавать класс или отдельные методы [`__aenter__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__aenter__) и [`__aexit__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__aexit__). Она должна применяться к функции [асинхронного генератора](https://python-all.ru/3.16/glossary.html#term-asynchronous-generator).7475Простой пример:7677```python78from contextlib import asynccontextmanager7980@asynccontextmanager81async def get_connection():82    conn = await acquire_db_connection()83    try:84        yield conn85    finally:86        await release_db_connection(conn)8788async def get_all_users():89    async with get_connection() as conn:90        return conn.query('SELECT ...')91```9293Добавлено в версии 3.7.9495Контекстные менеджеры, определенные с помощью [`@asynccontextmanager`](https://python-all.ru/3.16/library/contextlib.html#contextlib.asynccontextmanager), могут использоваться как в качестве декораторов, так и с операторами [`async with`](https://python-all.ru/3.16/reference/compound_stmts.html#async-with):9697```python98import time99from contextlib import asynccontextmanager100101@asynccontextmanager102async def timeit():103    now = time.monotonic()104    try:105        yield106    finally:107        print(f'it took {time.monotonic() - now}s to run')108109@timeit()110async def main():111    # ... асинхронный код ...112```113114При использовании в качестве декоратора для каждого вызова функции неявно создается новый экземпляр генератора. Это позволяет в противном случае «одноразовым» контекстным менеджерам, созданным с помощью [`@asynccontextmanager`](https://python-all.ru/3.16/library/contextlib.html#contextlib.asynccontextmanager), соответствовать требованию, согласно которому контекстные менеджеры должны поддерживать многократные вызовы, чтобы их можно было использовать в качестве декораторов.115116Изменено в версии 3.10: Асинхронные контекстные менеджеры, созданные с помощью [`@asynccontextmanager`](https://python-all.ru/3.16/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.16/reference/compound_stmts.html#with).145146> **Примечание**147>148> Большинство типов, управляющих ресурсами, поддерживают протокол [контекстного менеджера](https://python-all.ru/3.16/glossary.html#term-context-manager), который закрывает *thing* при выходе из оператора [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with). Поэтому `closing()` наиболее полезен для сторонних типов, не поддерживающих контекстные менеджеры. Этот пример приведен только для иллюстрации, поскольку [`urlopen()`](https://python-all.ru/3.16/library/urllib.request.html#urllib.request.urlopen) обычно используется в контекстном менеджере.149150#### `contextlib.aclosing(thing)`151152Возвращает асинхронный контекстный менеджер, который вызывает метод `aclose()` объекта *thing* после завершения блока. По сути эквивалентно следующему:153154```python155from contextlib import asynccontextmanager156157@asynccontextmanager158async def aclosing(thing):159    try:160        yield thing161    finally:162        await thing.aclose()163```164165Примечательно, что `aclosing()` поддерживает детерминированную очистку асинхронных генераторов, когда они завершаются досрочно из-за [`break`](https://python-all.ru/3.16/reference/simple_stmts.html#break) или исключения. Например:166167```python168from contextlib import aclosing169170async with aclosing(my_generator()) as values:171    async for value in values:172        if value == 42:173            break174```175176Этот шаблон гарантирует, что асинхронный код выхода генератора выполняется в том же контексте, что и его итерации (чтобы исключения и контекстные переменные работали как ожидается, а код выхода не выполнялся после завершения времени жизни задачи, от которой он зависит).177178Добавлено в версии 3.10.179180#### `contextlib.nullcontext(enter_result=None)`181182Возвращает менеджер контекста, который возвращает *enter\_result* из [`__enter__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__enter__), но в остальном ничего не делает. Предназначен для использования в качестве замены опционального менеджера контекста, например:183184```python185def myfunction(arg, ignore_exceptions=False):186    if ignore_exceptions:187        # Используйте suppress, чтобы игнорировать все исключения.188        cm = contextlib.suppress(Exception)189    else:190        # Не игнорируйте никакие исключения, cm не оказывает эффекта.191        cm = contextlib.nullcontext()192    with cm:193        # Выполнить действие194```195196Пример использования *enter\_result*:197198```python199def process_file(file_or_path):200    if isinstance(file_or_path, str):201        # Если строка, открыть файл202        cm = open(file_or_path)203    else:204        # Вызывающий отвечает за закрытие файла205        cm = nullcontext(file_or_path)206207    with cm as file:208        # Выполнить обработку файла209```210211Его также можно использовать в качестве замены [асинхронных менеджеров контекста](https://python-all.ru/3.16/reference/datamodel.html#async-context-managers):212213```python214async def send_http(session=None):215    if not session:216        # Если нет http-сессии, создать её с помощью aiohttp217        cm = aiohttp.ClientSession()218    else:219        # Вызывающий отвечает за закрытие сессии220        cm = nullcontext(session)221222    async with cm as session:223        # Отправить http-запросы с помощью сессии224```225226Добавлено в версии 3.7.227228Изменено в версии 3.10: добавлена поддержка [асинхронного менеджера контекста](https://python-all.ru/3.16/glossary.html#term-asynchronous-context-manager).229230#### `contextlib.suppress(*exceptions)`231232Возвращает менеджер контекста, который подавляет любые из указанных исключений, если они возникают в теле оператора `with`, а затем возобновляет выполнение с первого оператора после завершения оператора `with`.233234Как и любой другой механизм, полностью подавляющий исключения, этот менеджер контекста следует использовать только для обработки очень специфических ошибок, когда молчаливое продолжение выполнения программы является правильным решением.235236Например:237238```python239from contextlib import suppress240241with suppress(FileNotFoundError):242    os.remove('somefile.tmp')243244with suppress(FileNotFoundError):245    os.remove('someotherfile.tmp')246```247248Этот код эквивалентен:249250```python251try:252    os.remove('somefile.tmp')253except FileNotFoundError:254    pass255256try:257    os.remove('someotherfile.tmp')258except FileNotFoundError:259    pass260```261262Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.16/library/contextlib.html#reentrant-cms).263264Если код внутри блока `with` вызывает [`BaseExceptionGroup`](https://python-all.ru/3.16/library/exceptions.html#BaseExceptionGroup), подавленные исключения удаляются из группы. Любые исключения группы, которые не были подавлены, возбуждаются повторно в новой группе, созданной с помощью метода [`derive()`](https://python-all.ru/3.16/library/exceptions.html#BaseExceptionGroup.derive) исходной группы.265266Добавлено в версии 3.4.267268Изменено в версии 3.12: `suppress` теперь поддерживает подавление исключений, возбужденных в составе [`BaseExceptionGroup`](https://python-all.ru/3.16/library/exceptions.html#BaseExceptionGroup).269270#### `contextlib.redirect_stdout(new_target)`271272Контекстный менеджер для временного перенаправления [`sys.stdout`](https://python-all.ru/3.16/library/sys.html#sys.stdout) в другой [файловый объект](https://python-all.ru/3.16/glossary.html#term-file-object).273274Этот инструмент добавляет гибкость существующим функциям или классам, вывод которых жёстко привязан к [`sys.stdout`](https://python-all.ru/3.16/library/sys.html#sys.stdout).275276Например, вывод [`help()`](https://python-all.ru/3.16/library/functions.html#help) обычно отправляется в *sys.stdout*. Этот вывод можно захватить в строку, перенаправив его на объект [`io.StringIO`](https://python-all.ru/3.16/library/io.html#io.StringIO). Заменяющий поток данных возвращается из метода [`__enter__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__enter__) и поэтому доступен как цель оператора [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with):277278```python279with redirect_stdout(io.StringIO()) as f:280    help(pow)281s = f.getvalue()282```283284Чтобы отправить вывод [`help()`](https://python-all.ru/3.16/library/functions.html#help) в файл на диске, перенаправьте вывод в обычный файл:285286```python287with open('help.txt', 'w') as f:288    with redirect_stdout(f):289        help(pow)290```291292Чтобы отправить вывод [`help()`](https://python-all.ru/3.16/library/functions.html#help) в *sys.stderr*:293294```python295with redirect_stdout(sys.stderr):296    help(pow)297```298299Обратите внимание, что глобальный побочный эффект на [`sys.stdout`](https://python-all.ru/3.16/library/sys.html#sys.stdout) означает, что этот менеджер контекста не подходит для использования в библиотечном коде и большинстве многопоточных приложений. Он также не влияет на вывод подпроцессов. Тем не менее, это всё ещё полезный подход для многих вспомогательных скриптов.300301Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.16/library/contextlib.html#reentrant-cms).302303Добавлено в версии 3.4.304305#### `contextlib.redirect_stderr(new_target)`306307Аналогично [`redirect_stdout()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.redirect_stdout), но перенаправляет глобальный [`sys.stderr`](https://python-all.ru/3.16/library/sys.html#sys.stderr) в другой [файловый объект](https://python-all.ru/3.16/glossary.html#term-file-object).308309Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.16/library/contextlib.html#reentrant-cms).310311Добавлено в версии 3.5.312313#### `contextlib.chdir(path)`314315Менеджер контекста, небезопасный для параллельного использования, для смены текущей рабочей директории. Поскольку это меняет глобальное состояние – рабочую директорию, он не подходит для использования в большинстве многопоточных или асинхронных контекстов. Также он не подходит для нелинейного выполнения кода, например, генераторов, где выполнение программы временно приостанавливается – если только это не требуется явно, не следует выполнять yield, пока этот менеджер контекста активен.316317Это простая обёртка вокруг [`chdir()`](https://python-all.ru/3.16/library/os.html#os.chdir): она меняет текущую рабочую директорию при входе и восстанавливает предыдущую при выходе.318319Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.16/library/contextlib.html#reentrant-cms).320321Добавлено в версии 3.12.322323#### `class contextlib.ContextDecorator`324325Базовый класс, который позволяет менеджеру контекста также использоваться в качестве декоратора.326327Менеджеры контекста, наследующие от `ContextDecorator`, должны реализовывать [`__enter__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__enter__) и [`__exit__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__exit__) как обычно. `__exit__` сохраняет свою опциональную обработку исключений даже при использовании в качестве декоратора.328329`ContextDecorator` используется [`@contextmanager`](https://python-all.ru/3.16/library/contextlib.html#contextlib.contextmanager), поэтому эта функциональность доступна автоматически.330331Пример `ContextDecorator`:332333```python334from contextlib import ContextDecorator335336class mycontext(ContextDecorator):337    def __enter__(self):338        print('Starting')339        return self340341    def __exit__(self, *exc):342        print('Finishing')343        return False344```345346Затем класс можно использовать следующим образом:347348```python349>>> @mycontext()350... def function():351...     print('The bit in the middle')352...353>>> function()354Starting355The bit in the middle356Finishing357358>>> with mycontext():359...     print('The bit in the middle')360...361Starting362The bit in the middle363Finishing364```365366Это изменение – всего лишь синтаксический сахар для любой конструкции следующего вида:367368```python369def f():370    with cm():371        # Выполнить действия372```373374`ContextDecorator` позволяет вместо этого писать:375376```python377@cm()378def f():379    # Выполнить действия380```381382Становится ясно, что `cm` применяется ко всей функции, а не только к её части (и экономия уровня отступа тоже приятна).383384Существующие менеджеры контекста, уже имеющие базовый класс, можно расширить, используя `ContextDecorator` в качестве примеси (mixin):385386```python387from contextlib import ContextDecorator388389class mycontext(ContextBaseClass, ContextDecorator):390    def __enter__(self):391        return self392393    def __exit__(self, *exc):394        return False395```396397> **Примечание**398>399> Поскольку декорированная функция должна допускать многократный вызов, нижележащий менеджер контекста должен поддерживать использование в нескольких операторах [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with). Если это не так, следует использовать исходную конструкцию с явным оператором `with` внутри функции.400401Если декорируемая вызываемая сущность является функцией-генератором, корутинной функцией или асинхронной функцией-генератором, возвращаемая обёртка относится к тому же типу и держит контекстный менеджер открытым на протяжении всей итерации или ожидания, а не только на время вызова, создающего объект генератора или корутины. Обёрнутые генераторы и асинхронные генераторы явно закрываются по завершении итерации, как если бы через [`closing()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.closing) или [`aclosing()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.aclosing).402403> **Примечание**404>405> Для асинхронных генераторов обёртка повторно возвращает каждое значение через `async for`; значения, отправленные через [`asend()`](https://python-all.ru/3.16/reference/expressions.html#agen.asend), и исключения, возбуждённые через [`athrow()`](https://python-all.ru/3.16/reference/expressions.html#agen.athrow), не передаются обёрнутому генератору.406407Добавлено в версии 3.2.408409Изменено в версии 3.15: Декорирование функции-генератора, корутинной функции или асинхронной функции-генератора теперь держит контекстный менеджер открытым на протяжении итерации или ожидания. Ранее контекстный менеджер закрывался сразу после создания объекта генератора или корутины.410411#### `class contextlib.AsyncContextDecorator`412413Аналогично [`ContextDecorator`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ContextDecorator), но вход и выход из контекстного менеджера происходит через [`async with`](https://python-all.ru/3.16/reference/compound_stmts.html#async-with). Декорируйте корутинные функции и асинхронные функции-генераторы этим классом; возвращаемая обёртка относится к тому же типу.414415> **Примечание**416>417> Синхронные функции и генераторы принимаются, но обёртка всегда асинхронна, поэтому декорируемую вызываемую сущность затем нужно ожидать или итерировать через `async for`. Если такое изменение соглашения о вызове не предполагалось, используйте вместо этого [`ContextDecorator`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ContextDecorator).418419Пример `AsyncContextDecorator`:420421```python422from asyncio import run423from contextlib import AsyncContextDecorator424425class mycontext(AsyncContextDecorator):426    async def __aenter__(self):427        print('Starting')428        return self429430    async def __aexit__(self, *exc):431        print('Finishing')432        return False433```434435Затем класс можно использовать следующим образом:436437```python438>>> @mycontext()439... async def function():440...     print('The bit in the middle')441...442>>> run(function())443Starting444The bit in the middle445Finishing446447>>> async def function():448...    async with mycontext():449...         print('The bit in the middle')450...451>>> run(function())452Starting453The bit in the middle454Finishing455```456457Добавлено в версии 3.10.458459Изменено в версии 3.15: Декорирование асинхронной функции-генератора теперь держит контекстный менеджер открытым на протяжении итерации. Ранее контекстный менеджер закрывался сразу после создания объекта генератора. Синхронные функции и синхронные функции-генераторы теперь также принимаются, с возвратом асинхронной обёртки.460461#### `class contextlib.ExitStack`462463Менеджер контекста, предназначенный для упрощения программного комбинирования других менеджеров контекста и функций очистки, особенно тех, которые являются необязательными или определяются входными данными.464465Например, набор файлов можно легко обработать в одном операторе with следующим образом:466467```python468with ExitStack() as stack:469    files = [stack.enter_context(open(fname)) for fname in filenames]470    # Все открытые файлы будут автоматически закрыты в конце471    # оператор with, даже если попытки открыть файлы, идущие далее472    # в списке, вызывают исключение473```474475Метод [`__enter__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__enter__) возвращает экземпляр `ExitStack` и не выполняет никаких дополнительных операций.476477Каждый экземпляр поддерживает стек зарегистрированных колбэков, которые вызываются в обратном порядке при закрытии экземпляра (явно или неявно в конце оператора [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with)). Обратите внимание, что колбэки *не* вызываются неявно при сборке мусора экземпляра стека контекста.478479Такая стековая модель используется, чтобы менеджеры контекста, получающие свои ресурсы в методе `__init__` (например, файловые объекты), могли обрабатываться корректно.480481Поскольку зарегистрированные колбэки вызываются в порядке, обратном регистрации, в итоге это ведёт себя так, как если бы с зарегистрированным набором колбэков использовалось несколько вложенных операторов [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with). Это распространяется даже на обработку исключений: если внутренний колбэк подавляет или заменяет исключение, то внешним колбэкам будут переданы аргументы, основанные на этом обновлённом состоянии.482483Это относительно низкоуровневый API, который заботится о деталях корректной размотки стека колбэков выхода. Он предоставляет подходящую основу для менеджеров контекста более высокого уровня, которые манипулируют стеком выхода специфичным для приложения образом.484485Добавлено в версии 3.3.486487#### `enter_context(cm)`488489Входит в новый менеджер контекста и добавляет его метод [`__exit__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__exit__) в стек колбэков. Возвращаемое значение – результат собственного метода [`__enter__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__enter__) менеджера контекста.490491Эти менеджеры контекста могут подавлять исключения точно так же, как они это делали бы при непосредственном использовании в операторе [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with).492493Изменено в версии 3.11: Возбуждает [`TypeError`](https://python-all.ru/3.16/library/exceptions.html#TypeError) вместо [`AttributeError`](https://python-all.ru/3.16/library/exceptions.html#AttributeError), если *cm* не является менеджером контекста.494495Изменено в версии 3.15: Добавлена поддержка произвольных дескрипторов `__enter__()` и `__exit__()`.496497#### `push(exit)`498499Добавляет метод [`__exit__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__exit__) менеджера контекста в стек колбэков.500501Поскольку `__enter__` *не* вызывается, этот метод можно использовать для покрытия части реализации [`__enter__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__enter__) собственным методом [`__exit__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__exit__) менеджера контекста.502503Если передан объект, не являющийся менеджером контекста, этот метод считает его колбэком с той же сигнатурой, что и метод [`__exit__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__exit__) менеджера контекста, и добавляет его напрямую в стек колбэков.504505Возвращая истинные значения, эти колбэки могут подавлять исключения так же, как это делают методы [`__exit__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__exit__) менеджеров контекста.506507Переданный объект возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.508509Изменено в версии 3.15: Добавлена поддержка произвольных дескрипторов `__exit__()`.510511#### `callback(callback, /, *args, **kwds)`512513Принимает произвольную функцию-колбэк и аргументы и добавляет её в стек колбэков.514515В отличие от других методов, колбэки, добавленные таким образом, не могут подавлять исключения (поскольку им никогда не передаются сведения об исключении).516517Переданный колбэк возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.518519#### `pop_all()`520521Перемещает стек колбэков в новый экземпляр `ExitStack` и возвращает его. При этой операции колбэки не вызываются – вместо этого они будут вызваны при закрытии нового стека (явно или неявно в конце оператора [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with)).522523Например, группу файлов можно открыть как операцию «всё или ничего» следующим образом:524525```python526with ExitStack() as stack:527    files = [stack.enter_context(open(fname)) for fname in filenames]528    # Сохраните метод close, но пока не вызывайте его.529    close_files = stack.pop_all().close530    # Если открытие любого файла завершается неудачей, все ранее открытые файлы будут531    # закрыты автоматически. Если все файлы открыты успешно,532    # они останутся открытыми даже после завершения оператора with.533    # close_files() затем можно вызвать явно, чтобы закрыть их все.534```535536#### `close()`537538Немедленно разворачивает стек колбэков, вызывая колбэки в порядке, обратном регистрации. Для любых зарегистрированных менеджеров контекста и колбэков выхода переданные аргументы будут указывать, что исключения не произошло.539540#### `class contextlib.AsyncExitStack`541542[Асинхронный менеджер контекста](https://python-all.ru/3.16/reference/datamodel.html#async-context-managers), аналогичный [`ExitStack`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack), который поддерживает объединение как синхронных, так и асинхронных менеджеров контекста, а также имеет корутины для логики очистки.543544Метод [`close()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack.close) не реализован; вместо него следует использовать [`aclose()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.AsyncExitStack.aclose).545546#### `async enter_async_context(cm)`547548Аналогично [`ExitStack.enter_context()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack.enter_context), но ожидает асинхронный менеджер контекста.549550Изменено в версии 3.11: Вызывает [`TypeError`](https://python-all.ru/3.16/library/exceptions.html#TypeError) вместо [`AttributeError`](https://python-all.ru/3.16/library/exceptions.html#AttributeError), если *cm* не является асинхронным менеджером контекста.551552Изменено в версии 3.15: Добавлена поддержка произвольных дескрипторов `__aenter__()` и `__aexit__()`.553554#### `push_async_exit(exit)`555556Аналогично [`ExitStack.push()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack.push), но ожидает либо асинхронный менеджер контекста, либо корутинную функцию.557558Изменено в версии 3.15: Добавлена поддержка произвольных дескрипторов `__aexit__()`.559560#### `push_async_callback(callback, /, *args, **kwds)`561562Аналогично [`ExitStack.callback()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack.callback), но ожидает корутинную функцию.563564#### `async aclose()`565566Аналогично [`ExitStack.close()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack.close), но корректно обрабатывает ожидаемые объекты.567568Продолжая пример для [`@asynccontextmanager`](https://python-all.ru/3.16/library/contextlib.html#contextlib.asynccontextmanager):569570```python571async with AsyncExitStack() as stack:572    connections = [await stack.enter_async_context(get_connection())573        for i in range(5)]574    # Все открытые соединения будут автоматически освобождены в конце575    # оператора async with, даже если попытки открыть соединение576    # далее в списке вызывают исключение.577```578579Добавлено в версии 3.7.580581## Примеры и рецепты582583В этом разделе описаны некоторые примеры и рецепты для эффективного использования инструментов, предоставляемых `contextlib`.584585### Поддержка переменного числа менеджеров контекста586587Основной вариант использования [`ExitStack`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack) – это тот, что описан в документации класса: поддержка переменного числа менеджеров контекста и других операций очистки в одном операторе [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with). Переменность может быть обусловлена тем, что количество необходимых менеджеров контекста определяется пользовательским вводом (например, открытие указанной пользователем коллекции файлов), или тем, что некоторые менеджеры контекста являются необязательными:588589```python590with ExitStack() as stack:591    for resource in resources:592        stack.enter_context(resource)593    if need_special_resource():594        special = acquire_special_resource()595        stack.callback(release_special_resource, special)596    # Выполните операции, использующие полученные ресурсы597```598599Как показано, [`ExitStack`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack) также значительно упрощает использование операторов [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with) для управления произвольными ресурсами, которые изначально не поддерживают протокол управления контекстом.600601### Перехват исключений из методов `__enter__`602603Иногда бывает желательно перехватывать исключения из реализации метода [`__enter__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__enter__), *не* перехватывая случайно исключения из тела оператора [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with) или метода [`__exit__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__exit__) менеджера контекста. Используя [`ExitStack`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack), можно немного разделить шаги протокола управления контекстом, чтобы это стало возможным:604605```python606stack = ExitStack()607try:608    x = stack.enter_context(cm)609except Exception:610    # обработать исключение __enter__611else:612    with stack:613        # Обработка нормального случая614```615616Необходимость в этом, скорее всего, указывает на то, что базовый API должен предоставлять прямой интерфейс управления ресурсами для использования с операторами [`try`](https://python-all.ru/3.16/reference/compound_stmts.html#try)/[`except`](https://python-all.ru/3.16/reference/compound_stmts.html#except)/[`finally`](https://python-all.ru/3.16/reference/compound_stmts.html#finally), но не все API хорошо спроектированы в этом отношении. Когда менеджер контекста является единственным предоставляемым API управления ресурсами, тогда [`ExitStack`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack) может упростить обработку различных ситуаций, которые невозможно обработать напрямую в операторе [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with).617618### Очистка в реализации `__enter__`619620Как отмечено в документации [`ExitStack.push()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack.push), этот метод может быть полезен для очистки уже выделенного ресурса, если последующие шаги в реализации [`__enter__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__enter__) завершатся неудачей.621622Вот пример реализации этого для менеджера контекста, который принимает функции захвата и освобождения ресурса, а также опциональную функцию проверки, и отображает их на протокол управления контекстом:623624```python625from contextlib import contextmanager, AbstractContextManager, ExitStack626627class ResourceManager(AbstractContextManager):628629    def __init__(self, acquire_resource, release_resource, check_resource_ok=None):630        self.acquire_resource = acquire_resource631        self.release_resource = release_resource632        if check_resource_ok is None:633            def check_resource_ok(resource):634                return True635        self.check_resource_ok = check_resource_ok636637    @contextmanager638    def _cleanup_on_error(self):639        with ExitStack() as stack:640            stack.push(self)641            yield642            # Проверка прошла успешно и не вызвала исключение643            # Соответственно, мы хотим сохранить ресурс и передать его644            # обратно вызывающему коду645            stack.pop_all()646647    def __enter__(self):648        resource = self.acquire_resource()649        with self._cleanup_on_error():650            if not self.check_resource_ok(resource):651                msg = "Failed validation for {!r}"652                raise RuntimeError(msg.format(resource))653        return resource654655    def __exit__(self, *exc_details):656        # Нам не нужно дублировать логику освобождения ресурсов657        self.release_resource()658```659660### Замена любого использования `try-finally` и флаговых переменных661662Иногда встречается шаблон, в котором используется инструкция `try-finally` с переменной-флагом, указывающей, должно ли выполняться тело предложения `finally`. В простейшей форме (если это нельзя обработать простым использованием предложения `except`) это выглядит примерно так:663664```python665cleanup_needed = True666try:667    result = perform_operation()668    if result:669        cleanup_needed = False670finally:671    if cleanup_needed:672        cleanup_resources()673```674675Как и в любом коде, основанном на инструкции `try`, это может вызвать проблемы при разработке и рецензировании, поскольку код настройки и код очистки могут оказаться разделенными произвольно длинными участками кода.676677[`ExitStack`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack) позволяет вместо этого зарегистрировать колбэк для выполнения в конце инструкции `with`, а затем позже решить пропустить выполнение этого колбэка:678679```python680from contextlib import ExitStack681682with ExitStack() as stack:683    stack.callback(cleanup_resources)684    result = perform_operation()685    if result:686        stack.pop_all()687```688689Это позволяет заранее сделать предполагаемое поведение очистки явным, вместо того чтобы требовать отдельной переменной-флага.690691Если конкретное приложение часто использует этот шаблон, его можно упростить еще больше с помощью небольшого вспомогательного класса:692693```python694from contextlib import ExitStack695696class Callback(ExitStack):697    def __init__(self, callback, /, *args, **kwds):698        super().__init__()699        self.callback(callback, *args, **kwds)700701    def cancel(self):702        self.pop_all()703704with Callback(cleanup_resources) as cb:705    result = perform_operation()706    if result:707        cb.cancel()708```709710Если очистка ресурсов уже не аккуратно оформлена в виде отдельной функции, то все равно можно использовать форму декоратора [`ExitStack.callback()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack.callback), чтобы объявить очистку ресурсов заранее:711712```python713from contextlib import ExitStack714715with ExitStack() as stack:716    @stack.callback717    def cleanup_resources():718        ...719    result = perform_operation()720    if result:721        stack.pop_all()722```723724Из-за того, как работает протокол декоратора, функция обратного вызова, объявленная таким образом, не может принимать никаких параметров. Вместо этого любые ресурсы, которые необходимо освободить, должны быть доступны как переменные замыкания.725726### Использование менеджера контекста в качестве декоратора функции727728[`ContextDecorator`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ContextDecorator) позволяет использовать менеджер контекста как в обычной инструкции `with`, так и в качестве декоратора функции.729730Например, иногда полезно оборачивать функции или группы инструкций в логгер, который может отслеживать время входа и время выхода. Вместо написания как декоратора функции, так и менеджера контекста для этой задачи, наследование от [`ContextDecorator`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ContextDecorator) предоставляет обе возможности в одном определении:731732```python733from contextlib import ContextDecorator734import logging735736logging.basicConfig(level=logging.INFO)737738class track_entry_and_exit(ContextDecorator):739    def __init__(self, name):740        self.name = name741742    def __enter__(self):743        logging.info('Entering: %s', self.name)744745    def __exit__(self, exc_type, exc, exc_tb):746        logging.info('Exiting: %s', self.name)747```748749Экземпляры этого класса можно использовать как менеджер контекста:750751```python752with track_entry_and_exit('widget loader'):753    print('Some time consuming activity goes here')754    load_widget()755```756757А также как декоратор функции:758759```python760@track_entry_and_exit('widget loader')761def activity():762    print('Some time consuming activity goes here')763    load_widget()764```765766Обратите внимание, что есть одно дополнительное ограничение при использовании менеджеров контекста в качестве декораторов функций: невозможно получить доступ к возвращаемому значению [`__enter__()`](https://python-all.ru/3.16/reference/datamodel.html#object.__enter__). Если это значение необходимо, то все равно нужно использовать явную инструкцию `with`.767768> **См. также**769>770> **[**PEP 343**](https://python-all.ru/3.16/library/contextlib.html) – Оператор «with»**771>772> Спецификация, предыстория и примеры для инструкции [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with) в Python.773774## Одноразовые, многократно используемые и реентерабельные менеджеры контекста775776Большинство менеджеров контекста написаны таким образом, что их можно эффективно использовать только один раз в инструкции [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with). Эти одноразовые менеджеры контекста должны создаваться заново каждый раз при использовании – попытка использовать их второй раз вызовет исключение или иным образом приведет к некорректной работе.777778Это распространенное ограничение означает, что обычно рекомендуется создавать менеджеры контекста непосредственно в заголовке инструкции [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with), где они используются (как показано во всех примерах использования выше).779780Файлы являются примером эффективно одноразовых менеджеров контекста, поскольку первая инструкция [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with) закроет файл, предотвращая любые дальнейшие операции ввода-вывода с использованием этого объекта файла.781782Менеджеры контекста, созданные с помощью [`@contextmanager`](https://python-all.ru/3.16/library/contextlib.html#contextlib.contextmanager), также являются одноразовыми менеджерами контекста и будут сообщать об ошибке, если базовый генератор не сможет выполнить yield при попытке использовать их второй раз:783784```python785>>> from contextlib import contextmanager786>>> @contextmanager787... def singleuse():788...     print("Before")789...     yield790...     print("After")791...792>>> cm = singleuse()793>>> with cm:794...     pass795...796Before797After798>>> with cm:799...     pass800...801Traceback (most recent call last):802    ...803RuntimeError: generator didn't yield804```805806### Реентерабельные менеджеры контекста807808Более сложные менеджеры контекста могут быть «реентерабельными». Такие менеджеры контекста можно использовать не только в нескольких инструкциях [`with`](https://python-all.ru/3.16/reference/compound_stmts.html#with), но также *внутри* инструкции `with`, которая уже использует тот же менеджер контекста.809810[`threading.RLock`](https://python-all.ru/3.16/library/threading.html#threading.RLock) является примером реентерабельного менеджера контекста, как и [`suppress()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.suppress), [`redirect_stdout()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.redirect_stdout) и [`chdir()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.chdir). Вот очень простой пример реентерабельного использования:811812```python813>>> from contextlib import redirect_stdout814>>> from io import StringIO815>>> stream = StringIO()816>>> write_to_stream = redirect_stdout(stream)817>>> with write_to_stream:818...     print("This is written to the stream rather than stdout")819...     with write_to_stream:820...         print("This is also written to the stream")821...822>>> print("This is written directly to stdout")823This is written directly to stdout824>>> print(stream.getvalue())825This is written to the stream rather than stdout826This is also written to the stream827```828829Реальные примеры реентерабельности, скорее всего, будут включать несколько функций, вызывающих друг друга, и, следовательно, будут гораздо сложнее этого примера.830831Также обратите внимание, что быть реентерабельным – это *не* то же самое, что быть потокобезопасным. [`redirect_stdout()`](https://python-all.ru/3.16/library/contextlib.html#contextlib.redirect_stdout), например, определенно не является потокобезопасным, так как он вносит глобальное изменение в состояние системы, привязывая [`sys.stdout`](https://python-all.ru/3.16/library/sys.html#sys.stdout) к другому потоку данных.832833### Многократно используемые менеджеры контекста834835Отличающиеся от одноразовых и реентерабельных менеджеров контекста – «многократно используемые» менеджеры контекста (или, если быть совсем точным, «многократно используемые, но не реентерабельные» менеджеры контекста, поскольку реентерабельные менеджеры контекста также являются многократно используемыми). Эти менеджеры контекста поддерживают многократное использование, но завершатся ошибкой (или иным образом будут работать некорректно), если конкретный экземпляр менеджера контекста уже был использован во внешней инструкции with.836837[`threading.Lock`](https://python-all.ru/3.16/library/threading.html#threading.Lock) является примером многократно используемого, но не реентерабельного менеджера контекста (для реентерабельной блокировки необходимо использовать [`threading.RLock`](https://python-all.ru/3.16/library/threading.html#threading.RLock) вместо этого).838839Другим примером многократно используемого, но не реентерабельного менеджера контекста является [`ExitStack`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack), так как он вызывает *все* зарегистрированные в данный момент колбэки при выходе из любой инструкции with, независимо от того, где эти колбэки были добавлены:840841```python842>>> from contextlib import ExitStack843>>> stack = ExitStack()844>>> with stack:845...     stack.callback(print, "Callback: from first context")846...     print("Leaving first context")847...848Leaving first context849Callback: from first context850>>> with stack:851...     stack.callback(print, "Callback: from second context")852...     print("Leaving second context")853...854Leaving second context855Callback: from second context856>>> with stack:857...     stack.callback(print, "Callback: from outer context")858...     with stack:859...         stack.callback(print, "Callback: from inner context")860...         print("Leaving inner context")861...     print("Leaving outer context")862...863Leaving inner context864Callback: from inner context865Callback: from outer context866Leaving outer context867```868869Как показывает вывод примера, повторное использование одного объекта стека в нескольких инструкциях with работает корректно, но попытка вложить их приведет к очистке стека в конце самой внутренней инструкции with, что вряд ли является желательным поведением.870871Использование отдельных экземпляров [`ExitStack`](https://python-all.ru/3.16/library/contextlib.html#contextlib.ExitStack) вместо повторного использования одного экземпляра позволяет избежать этой проблемы:872873```python874>>> from contextlib import ExitStack875>>> with ExitStack() as outer_stack:876...     outer_stack.callback(print, "Callback: from outer context")877...     with ExitStack() as inner_stack:878...         inner_stack.callback(print, "Callback: from inner context")879...         print("Leaving inner context")880...     print("Leaving outer context")881...882Leaving inner context883Callback: from inner context884Leaving outer context885Callback: from outer context886```887