> **Источник:** https://python-all.ru/3.3/library/contextlib.html
>
> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.

---

# 28.6. [`contextlib`](https://python-all.ru/3.3/library/contextlib.html#module-contextlib) – Утилиты для контекстов оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with)

**Исходный код:** [Lib/contextlib.py](https://python-all.ru/src/3.3/Lib/contextlib.py)

---

Этот модуль предоставляет утилиты для типичных задач, связанных с оператором [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with). Дополнительную информацию см. также в разделах [*Типы менеджеров контекста*](https://python-all.ru/3.3/library/stdtypes.html#typecontextmanager) и [*Менеджеры контекста оператора with*](https://python-all.ru/3.3/reference/datamodel.html#context-managers).

## 28.6.1. Утилиты

Предоставляемые функции и классы:

#### `@contextlib.contextmanager`

Эта функция – [*декоратор*](https://python-all.ru/3.3/glossary.html#term-decorator), который можно использовать для определения фабричной функции для менеджеров контекста оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with), без необходимости создавать класс или отдельные методы [`__enter__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__enter__) и [`__exit__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__exit__).

Простой пример (это не рекомендуется как реальный способ генерации HTML!):

```python
from contextlib import contextmanager

@contextmanager
def tag(name):
    print("<%s>" % name)
    yield
    print("</%s>" % name)

>>> with tag("h1"):
...    print("foo")
...
<h1>
foo
</h1>
```

Декорируемая функция при вызове должна возвращать [*генератор*](https://python-all.ru/3.3/glossary.html#term-generator)-итератор. Этот итератор должен передать (yield) ровно одно значение, которое будет связано с целями в предложении [`as`](https://python-all.ru/3.3/reference/compound_stmts.html#as) оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with), если оно есть.

В точке, где генератор выполняет yield, выполняется блок, вложенный в оператор [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with). Затем генератор возобновляется после выхода из блока. Если в блоке возникает необработанное исключение, оно повторно возбуждается внутри генератора в точке, где произошёл yield. Таким образом, можно использовать конструкцию [`try`](https://python-all.ru/3.3/reference/compound_stmts.html#try)...[`except`](https://python-all.ru/3.3/reference/compound_stmts.html#except)...[`finally`](https://python-all.ru/3.3/reference/compound_stmts.html#finally) для перехвата ошибки (если она есть) или для обеспечения выполнения очистки. Если исключение перехватывается лишь для того, чтобы залогировать его или выполнить какое-либо действие (а не подавить его полностью), генератор должен возобновить (reraise) это исключение. В противном случае менеджер контекста-генератор сообщит оператору [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with), что исключение обработано, и выполнение продолжится с оператора, непосредственно следующего за оператором [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with).

[`contextmanager()`](https://python-all.ru/3.3/library/contextlib.html#contextlib.contextmanager) использует [`ContextDecorator`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ContextDecorator), поэтому создаваемые им менеджеры контекста можно использовать как в качестве декораторов, так и в операторах [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with). При использовании в качестве декоратора новый экземпляр генератора неявно создаётся при каждом вызове функции (это позволяет менеджерам контекста, созданным [`contextmanager()`](https://python-all.ru/3.3/library/contextlib.html#contextlib.contextmanager), которые в противном случае были бы «одноразовыми», удовлетворять требованию, что менеджеры контекста должны поддерживать множественные вызовы, чтобы использоваться в качестве декораторов).

Изменено в версии 3.2: Использование [`ContextDecorator`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ContextDecorator).

#### `contextlib.closing(thing)`

Возвращает контекстный менеджер, который закрывает *thing* после завершения блока. По сути эквивалентно следующему:

```python
from contextlib import contextmanager

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()
```

И позволяет писать код следующим образом:

```python
from contextlib import closing
from urllib.request import urlopen

with closing(urlopen('http://www.python.org')) as page:
    for line in page:
        print(line)
```

без необходимости явно закрывать `page`. Даже если произойдёт ошибка, `page.close()` будет вызван при выходе из блока [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with).

#### `class contextlib.ContextDecorator`

Базовый класс, который позволяет менеджеру контекста также использоваться в качестве декоратора.

Менеджеры контекста, наследующие от `ContextDecorator`, должны реализовывать `__enter__` и `__exit__` как обычно. `__exit__` сохраняет свою опциональную обработку исключений даже при использовании в качестве декоратора.

`ContextDecorator` используется [`contextmanager()`](https://python-all.ru/3.3/library/contextlib.html#contextlib.contextmanager), поэтому эта функциональность доступна автоматически.

Пример `ContextDecorator`:

```python
from contextlib import ContextDecorator

class mycontext(ContextDecorator):
    def __enter__(self):
        print('Starting')
        return self

    def __exit__(self, *exc):
        print('Finishing')
        return False

>>> @mycontext()
... def function():
...     print('The bit in the middle')
...
>>> function()
Starting
The bit in the middle
Finishing

>>> with mycontext():
...     print('The bit in the middle')
...
Starting
The bit in the middle
Finishing
```

Это изменение – всего лишь синтаксический сахар для любой конструкции следующего вида:

```python
def f():
    with cm():
        # Выполнить действия
```

`ContextDecorator` позволяет вместо этого написать:

```python
@cm()
def f():
    # Выполнить действия
```

Это даёт понять, что `cm` применяется ко всей функции, а не только к её части (и экономия уровня отступа тоже приятна).

Существующие менеджеры контекста, у которых уже есть базовый класс, могут быть расширены путём использования `ContextDecorator` в качестве примесного класса (mixin class):

```python
from contextlib import ContextDecorator

class mycontext(ContextBaseClass, ContextDecorator):
    def __enter__(self):
        return self

    def __exit__(self, *exc):
        return False
```

> **Примечание**
>
> Поскольку декорированная функция должна допускать многократный вызов, базовый менеджер контекста должен поддерживать использование в нескольких операторах [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with). Если это не так, то следует использовать исходную конструкцию с явным оператором [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with) внутри функции.

Новое в версии 3.2.

#### `class contextlib.ExitStack`

Менеджер контекста, предназначенный для упрощения программного комбинирования других менеджеров контекста и функций очистки, особенно тех, которые являются необязательными или определяются входными данными.

Например, набор файлов можно легко обработать в одном операторе with следующим образом:

```python
with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # Все открытые файлы будут автоматически закрыты в конце
    # оператор with, даже если попытки открыть файлы, идущие далее
    # в списке, вызывают исключение
```

Каждый экземпляр поддерживает стек зарегистрированных колбэков, которые вызываются в обратном порядке при закрытии экземпляра (явно или неявно в конце оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with)). Обратите внимание, что колбэки *не* вызываются неявно при сборке мусора экземпляра стека контекстов.

Эта модель стека используется, чтобы менеджеры контекста, которые получают свои ресурсы в методе `__init__` (например, файловые объекты), могли быть обработаны корректно.

Поскольку зарегистрированные колбэки вызываются в обратном порядке регистрации, в итоге это ведёт себя так, как если бы с зарегистрированным набором колбэков использовались несколько вложенных операторов [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with). Это распространяется даже на обработку исключений: если внутренний колбэк подавляет или заменяет исключение, то внешние колбэки получат аргументы на основе этого обновлённого состояния.

Это относительно низкоуровневый API, который заботится о деталях корректной размотки стека колбэков выхода. Он предоставляет подходящую основу для менеджеров контекста более высокого уровня, которые манипулируют стеком выхода специфичным для приложения образом.

Новое в версии 3.3.

#### `enter_context(cm)`

Входит в новый менеджер контекста и добавляет его метод [`__exit__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__exit__) в стек колбэков. Возвращаемое значение – результат собственного метода менеджера контекста [`__enter__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__enter__).

Эти менеджеры контекста могут подавлять исключения так же, как если бы они использовались непосредственно как часть оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with).

#### `push(exit)`

Добавляет метод [`__exit__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__exit__) менеджера контекста в стек колбэков.

Поскольку `__enter__` *не* вызывается, этот метод можно использовать, чтобы покрыть часть реализации [`__enter__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__enter__) собственным методом [`__exit__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__exit__) менеджера контекста.

Если передан объект, не являющийся менеджером контекста, этот метод считает, что это колбэк с той же сигнатурой, что и метод [`__exit__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__exit__) менеджера контекста, и добавляет его напрямую в стек колбэков.

Возвращая истинные значения, эти колбэки могут подавлять исключения так же, как это делают методы [`__exit__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__exit__) менеджера контекста.

Переданный объект возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.

#### `callback(callback, *args, **kwds)`

Принимает произвольную функцию-колбэк и аргументы и добавляет её в стек колбэков.

В отличие от других методов, колбэки, добавленные таким образом, не могут подавлять исключения (поскольку им никогда не передаются сведения об исключении).

Переданный колбэк возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.

#### `pop_all()`

Передаёт стек колбэков новому экземпляру [`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack) и возвращает его. Ни один колбэк при этой операции не вызывается – вместо этого они будут вызваны при закрытии нового стека (явно или неявно в конце оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with)).

Например, группу файлов можно открыть как операцию «всё или ничего» следующим образом:

```python
with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # Сохраните метод close, но пока не вызывайте его.
    close_files = stack.pop_all().close
    # Если открытие любого файла завершается неудачей, все ранее открытые файлы будут
    # закрыты автоматически. Если все файлы открыты успешно,
    # они останутся открытыми даже после завершения оператора with.
    # close_files() затем можно вызвать явно, чтобы закрыть их все.
```

#### `close()`

Немедленно разворачивает стек колбэков, вызывая колбэки в порядке, обратном регистрации. Для любых зарегистрированных менеджеров контекста и колбэков выхода переданные аргументы будут указывать, что исключения не произошло.

## 28.6.2. Примеры и рецепты

В этом разделе описаны некоторые примеры и рецепты для эффективного использования инструментов, предоставляемых модулем [`contextlib`](https://python-all.ru/3.3/library/contextlib.html#module-contextlib).

### 28.6.2.1. Поддержка переменного количества менеджеров контекста

Основной вариант использования [`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack) описан в документации класса: поддержка переменного числа менеджеров контекста и других операций очистки в одном операторе [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with). Вариативность может быть обусловлена тем, что количество необходимых менеджеров контекста определяется пользовательским вводом (например, открытие указанной пользователем коллекции файлов), или тем, что некоторые менеджеры контекста являются необязательными:

```python
with ExitStack() as stack:
    for resource in resources:
        stack.enter_context(resource)
    if need_special resource:
        special = acquire_special_resource()
        stack.callback(release_special_resource, special)
    # Выполните операции, использующие полученные ресурсы
```

Как показано, [`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack) также позволяет довольно легко использовать операторы [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with) для управления произвольными ресурсами, которые изначально не поддерживают протокол управления контекстом.

### 28.6.2.2. Упрощение поддержки одного опционального менеджера контекста

В частном случае одного необязательного менеджера контекста экземпляры [`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack) можно использовать как «ничего не делающий» менеджер контекста, что позволяет легко опустить менеджер контекста, не влияя на общую структуру исходного кода:

```python
def debug_trace(details):
    if __debug__:
        return TraceContext(details)
    # В релизном режиме никаких особых действий с контекстом не выполняется
    return ExitStack()

with debug_trace():
    # В режиме отладки набор трассируется, в остальных случаях работает обычным образом
```

### 28.6.2.3. Перехват исключений из методов `__enter__`

Иногда бывает желательно перехватывать исключения из реализации метода `__enter__`, *не* перехватывая случайно исключения из тела оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with) или метода `__exit__` менеджера контекста. Используя [`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack), можно немного разделить шаги протокола управления контекстом, чтобы добиться этого:

```python
stack = ExitStack()
try:
    x = stack.enter_context(cm)
except Exception:
    # обработать исключение __enter__
else:
    with stack:
        # Обработка нормального случая
```

Необходимость в этом, вероятно, указывает на то, что базовый API должен предоставлять прямой интерфейс управления ресурсами для использования с операторами [`try`](https://python-all.ru/3.3/reference/compound_stmts.html#try)/[`except`](https://python-all.ru/3.3/reference/compound_stmts.html#except)/[`finally`](https://python-all.ru/3.3/reference/compound_stmts.html#finally), но не все API хорошо спроектированы в этом отношении. Когда менеджер контекста является единственным предоставляемым API управления ресурсами, [`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack) может упростить обработку различных ситуаций, которые невозможно обработать напрямую в операторе [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with).

### 28.6.2.4. Очистка в реализации `__enter__`

Как отмечено в документации [`ExitStack.push()`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack.push), этот метод может быть полезен для очистки уже выделенного ресурса, если последующие шаги в реализации [`__enter__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__enter__) завершатся неудачей.

Вот пример реализации этого для менеджера контекста, который принимает функции захвата и освобождения ресурса, а также опциональную функцию проверки, и отображает их на протокол управления контекстом:

```python
from contextlib import contextmanager, ExitStack

class ResourceManager:

    def __init__(self, acquire_resource, release_resource, check_resource_ok=None):
        self.acquire_resource = acquire_resource
        self.release_resource = release_resource
        if check_resource_ok is None:
            def check_resource_ok(resource):
                return True
        self.check_resource_ok = check_resource_ok

    @contextmanager
    def _cleanup_on_error(self):
        with ExitStack() as stack:
            stack.push(self)
            yield
            # Проверка прошла успешно и не вызвала исключение
            # Соответственно, мы хотим сохранить ресурс и передать его
            # обратно вызывающему коду
            stack.pop_all()

    def __enter__(self):
        resource = self.acquire_resource()
        with self._cleanup_on_error():
            if not self.check_resource_ok(resource):
                msg = "Failed validation for {!r}"
                raise RuntimeError(msg.format(resource))
        return resource

    def __exit__(self, *exc_details):
        # Нам не нужно дублировать логику освобождения ресурсов
        self.release_resource()
```

### 28.6.2.5. Замена любого использования `try-finally` и флаговых переменных

Иногда можно встретить шаблон с оператором `try-finally` и флаговой переменной, указывающей, должно ли выполняться тело пункта `finally`. В своей простейшей форме (которую нельзя обработать простым использованием пункта `except`) это выглядит примерно так:

```python
cleanup_needed = True
try:
    result = perform_operation()
    if result:
        cleanup_needed = False
finally:
    if cleanup_needed:
        cleanup_resources()
```

Как и любой код на основе оператора `try`, это может создавать проблемы при разработке и проверке, поскольку код настройки и код очистки могут оказаться разделёнными произвольно длинными участками кода.

[`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack) позволяет вместо этого зарегистрировать колбэк для выполнения в конце оператора `with`, а затем позже решить пропустить выполнение этого колбэка:

```python
from contextlib import ExitStack

with ExitStack() as stack:
    stack.callback(cleanup_resources)
    result = perform_operation()
    if result:
        stack.pop_all()
```

Это позволяет заранее явно указать предполагаемое поведение очистки, вместо того чтобы требовать отдельную переменную-флаг.

Если конкретное приложение часто использует этот шаблон, его можно упростить еще больше с помощью небольшого вспомогательного класса:

```python
from contextlib import ExitStack

class Callback(ExitStack):
    def __init__(self, callback, *args, **kwds):
        super(Callback, self).__init__()
        self.callback(callback, *args, **kwds)

    def cancel(self):
        self.pop_all()

with Callback(cleanup_resources) as cb:
    result = perform_operation()
    if result:
        cb.cancel()
```

Если очистка ресурса ещё не аккуратно оформлена в виде отдельной функции, всё равно можно использовать форму декоратора [`ExitStack.callback()`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack.callback), чтобы объявить очистку ресурса заранее:

```python
from contextlib import ExitStack

with ExitStack() as stack:
    @stack.callback
    def cleanup_resources():
        ...
    result = perform_operation()
    if result:
        stack.pop_all()
```

Из-за того, как работает протокол декоратора, функция обратного вызова, объявленная таким образом, не может принимать никаких параметров. Вместо этого любые ресурсы, которые необходимо освободить, должны быть доступны как замыкающие переменные.

### 28.6.2.6. Использование менеджера контекста в качестве декоратора функции

[`ContextDecorator`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ContextDecorator) позволяет использовать менеджер контекста как в обычном операторе `with`, так и в качестве декоратора функции.

Например, иногда бывает полезно обернуть функции или группы операторов в логгер, который может отслеживать время входа и время выхода. Вместо того чтобы писать и декоратор функции, и менеджер контекста для этой задачи, наследование от [`ContextDecorator`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ContextDecorator) предоставляет обе возможности в одном определении:

```python
from contextlib import ContextDecorator
import logging

logging.basicConfig(level=logging.INFO)

class track_entry_and_exit(ContextDecorator):
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        logging.info('Entering: {}'.format(name))

    def __exit__(self, exc_type, exc, exc_tb):
        logging.info('Exiting: {}'.format(name))
```

Экземпляры этого класса можно использовать как менеджер контекста:

```python
with track_entry_and_exit('widget loader'):
    print('Some time consuming activity goes here')
    load_widget()
```

А также как декоратор функции:

```python
@track_entry_and_exit('widget loader')
def activity():
    print('Some time consuming activity goes here')
    load_widget()
```

Обратите внимание, что есть ещё одно ограничение при использовании менеджеров контекста в качестве декораторов функций: нет способа получить доступ к возвращаемому значению [`__enter__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__enter__). Если это значение необходимо, всё равно нужно использовать явный оператор `with`.

> **См. также**
>
> **[**PEP 0343**](https://python-all.ru/3.3/library/contextlib.html) – оператор «with»**
>
> Спецификация, предыстория и примеры для оператора
>
> [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with)
>
> в Python.
