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

contextlib.md

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

1> **Источник:** https://python-all.ru/3.6/library/contextlib.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 29.6. [`contextlib`](https://python-all.ru/3.6/library/contextlib.html#module-contextlib) – Утилиты для контекстов оператора [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with)89**Исходный код:** [Lib/contextlib.py](https://python-all.ru/src/3.6/Lib/contextlib.py)1011---1213Этот модуль предоставляет утилиты для типовых задач, связанных с оператором [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with). Для получения дополнительной информации см. также [Типы менеджеров контекста](https://python-all.ru/3.6/library/stdtypes.html#typecontextmanager) и [Менеджеры контекста оператора with](https://python-all.ru/3.6/reference/datamodel.html#context-managers).1415## 29.6.1. Утилиты1617Предоставляемые функции и классы:1819#### `class contextlib.AbstractContextManager`2021[абстрактный базовый класс](https://python-all.ru/3.6/glossary.html#term-abstract-base-class) для классов, реализующих [`object.__enter__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__enter__) и [`object.__exit__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__exit__). Предоставляется реализация по умолчанию для [`object.__enter__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__enter__), которая возвращает `self`, а [`object.__exit__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__exit__) – это абстрактный метод, который по умолчанию возвращает `None`. См. также определение [Типов менеджеров контекста](https://python-all.ru/3.6/library/stdtypes.html#typecontextmanager).2223Новое в версии 3.6.2425#### `@contextlib.contextmanager`2627Эта функция – [декоратор](https://python-all.ru/3.6/glossary.html#term-decorator), который можно использовать для определения фабричной функции для контекстных менеджеров оператора [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with), без необходимости создавать класс или отдельные методы [`__enter__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__enter__) и [`__exit__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__exit__).2829Хотя многие объекты изначально поддерживают использование в with-выражениях, иногда требуется управлять ресурсом, который сам по себе не является менеджером контекста, и не реализует метод `close()` для использования с `contextlib.closing`3031Абстрактным примером может служить следующий код для обеспечения корректного управления ресурсами:3233```python34from contextlib import contextmanager3536@contextmanager37def managed_resource(*args, **kwds):38    # Код для получения ресурса, например:39    resource = acquire_resource(*args, **kwds)40    try:41        yield resource42    finally:43        # Код для освобождения ресурса, например:44        release_resource(resource)4546>>> with managed_resource(timeout=3600) as resource:47...     # Ресурс освобождается в конце этого блока,48...     # даже если код в блоке вызывает исключение49```5051Декорируемая функция при вызове должна возвращать [генератор](https://python-all.ru/3.6/glossary.html#term-generator)-итератор. Этот итератор должен возвращать ровно одно значение, которое будет связано с целями в предложении [`as`](https://python-all.ru/3.6/reference/compound_stmts.html#as) оператора [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with), если таковые имеются.5253В момент, где генератор делает yield, выполняется блок, вложенный в оператор [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with). Затем генератор возобновляется после выхода из блока. Если в блоке возникает необработанное исключение, оно повторно возбуждается внутри генератора в точке, где произошел yield. Таким образом, можно использовать оператор [`try`](https://python-all.ru/3.6/reference/compound_stmts.html#try)…[`except`](https://python-all.ru/3.6/reference/compound_stmts.html#except)…[`finally`](https://python-all.ru/3.6/reference/compound_stmts.html#finally) для перехвата ошибки (если есть) или для выполнения очистки. Если исключение перехватывается только для того, чтобы зарегистрировать его или выполнить какое-либо действие (а не подавить полностью), генератор должен повторно возбудить это исключение. В противном случае контекстный менеджер генератора сообщит оператору [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with), что исключение обработано, и выполнение продолжится с оператора, следующего сразу за оператором [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with).5455[`contextmanager()`](https://python-all.ru/3.6/library/contextlib.html#contextlib.contextmanager) использует [`ContextDecorator`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ContextDecorator), поэтому создаваемые им контекстные менеджеры могут использоваться как в качестве декораторов, так и в операторах [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with). При использовании в качестве декоратора для каждого вызова функции неявно создается новый экземпляр генератора (это позволяет в противном случае «одноразовым» контекстным менеджерам, созданным с помощью [`contextmanager()`](https://python-all.ru/3.6/library/contextlib.html#contextlib.contextmanager), соответствовать требованию, согласно которому контекстные менеджеры должны поддерживать многократные вызовы, чтобы их можно было использовать в качестве декораторов).5657Изменено в версии 3.2: Использование [`ContextDecorator`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ContextDecorator).5859#### `contextlib.closing(thing)`6061Возвращает контекстный менеджер, который закрывает *thing* после завершения блока. По сути эквивалентно следующему:6263```python64from contextlib import contextmanager6566@contextmanager67def closing(thing):68    try:69        yield thing70    finally:71        thing.close()72```7374И позволяет писать код следующим образом:7576```python77from contextlib import closing78from urllib.request import urlopen7980with closing(urlopen('http://www.python.org')) as page:81    for line in page:82        print(line)83```8485без необходимости явно закрывать `page`. Даже если произойдет ошибка, `page.close()` будет вызван при выходе из блока [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with).8687#### `contextlib.suppress(*exceptions)`8889Возвращает менеджер контекста, который подавляет любые из указанных исключений, если они возникают в теле оператора with, а затем возобновляет выполнение с первого оператора после конца оператора with.9091Как и любой другой механизм, полностью подавляющий исключения, этот менеджер контекста следует использовать только для обработки очень специфических ошибок, когда молчаливое продолжение выполнения программы является правильным решением.9293Например:9495```python96from contextlib import suppress9798with suppress(FileNotFoundError):99    os.remove('somefile.tmp')100101with suppress(FileNotFoundError):102    os.remove('someotherfile.tmp')103```104105Этот код эквивалентен:106107```python108try:109    os.remove('somefile.tmp')110except FileNotFoundError:111    pass112113try:114    os.remove('someotherfile.tmp')115except FileNotFoundError:116    pass117```118119Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.6/library/contextlib.html#reentrant-cms).120121Новое в версии 3.4.122123#### `contextlib.redirect_stdout(new_target)`124125Менеджер контекста для временного перенаправления [`sys.stdout`](https://python-all.ru/3.6/library/sys.html#sys.stdout) в другой файл или файлоподобный объект.126127Этот инструмент добавляет гибкость существующим функциям или классам, чей вывод жестко привязан к stdout.128129Например, вывод [`help()`](https://python-all.ru/3.6/library/functions.html#help) обычно направляется в *sys.stdout*. Вы можете захватить этот вывод в строку, перенаправив вывод в [`io.StringIO`](https://python-all.ru/3.6/library/io.html#io.StringIO) объект:130131```python132f = io.StringIO()133with redirect_stdout(f):134    help(pow)135s = f.getvalue()136```137138Чтобы отправить вывод [`help()`](https://python-all.ru/3.6/library/functions.html#help) в файл на диске, перенаправьте вывод в обычный файл:139140```python141with open('help.txt', 'w') as f:142    with redirect_stdout(f):143        help(pow)144```145146Чтобы отправить вывод [`help()`](https://python-all.ru/3.6/library/functions.html#help) в *sys.stderr*:147148```python149with redirect_stdout(sys.stderr):150    help(pow)151```152153Обратите внимание, что глобальный побочный эффект на [`sys.stdout`](https://python-all.ru/3.6/library/sys.html#sys.stdout) означает, что этот менеджер контекста не подходит для использования в библиотечном коде и большинстве многопоточных приложений. Он также не влияет на вывод подпроцессов. Тем не менее, это всё ещё полезный подход для многих вспомогательных скриптов.154155Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.6/library/contextlib.html#reentrant-cms).156157Новое в версии 3.4.158159#### `contextlib.redirect_stderr(new_target)`160161Аналогично [`redirect_stdout()`](https://python-all.ru/3.6/library/contextlib.html#contextlib.redirect_stdout), но перенаправляет [`sys.stderr`](https://python-all.ru/3.6/library/sys.html#sys.stderr) в другой файл или файлоподобный объект.162163Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.6/library/contextlib.html#reentrant-cms).164165Новое в версии 3.5.166167#### `class contextlib.ContextDecorator`168169Базовый класс, который позволяет менеджеру контекста также использоваться в качестве декоратора.170171Менеджеры контекста, наследующие от `ContextDecorator`, должны обычным образом реализовывать `__enter__` и `__exit__`. `__exit__` сохраняет необязательную обработку исключений даже при использовании в качестве декоратора.172173`ContextDecorator` используется [`contextmanager()`](https://python-all.ru/3.6/library/contextlib.html#contextlib.contextmanager), поэтому эта функциональность доступна автоматически.174175Пример `ContextDecorator`:176177```python178from contextlib import ContextDecorator179180class mycontext(ContextDecorator):181    def __enter__(self):182        print('Starting')183        return self184185    def __exit__(self, *exc):186        print('Finishing')187        return False188189>>> @mycontext()190... def function():191...     print('The bit in the middle')192...193>>> function()194Starting195The bit in the middle196Finishing197198>>> with mycontext():199...     print('The bit in the middle')200...201Starting202The bit in the middle203Finishing204```205206Это изменение – всего лишь синтаксический сахар для любой конструкции следующего вида:207208```python209def f():210    with cm():211        # Выполнить действия212```213214`ContextDecorator` позволяет вместо этого писать:215216```python217@cm()218def f():219    # Выполнить действия220```221222Становится ясно, что `cm` применяется ко всей функции, а не только к её части (и экономия уровня отступа тоже приятна).223224Существующие менеджеры контекста, уже имеющие базовый класс, можно расширить, используя `ContextDecorator` в качестве примеси (mixin):225226```python227from contextlib import ContextDecorator228229class mycontext(ContextBaseClass, ContextDecorator):230    def __enter__(self):231        return self232233    def __exit__(self, *exc):234        return False235```236237> **Примечание**238>239> Поскольку декорированная функция должна допускать многократный вызов, нижележащий менеджер контекста должен поддерживать использование в нескольких операторах [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with). Если это не так, следует использовать исходную конструкцию с явным оператором [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with) внутри функции.240241Новое в версии 3.2.242243#### `class contextlib.ExitStack`244245Менеджер контекста, предназначенный для упрощения программного комбинирования других менеджеров контекста и функций очистки, особенно тех, которые являются необязательными или определяются входными данными.246247Например, набор файлов можно легко обработать в одном операторе with следующим образом:248249```python250with ExitStack() as stack:251    files = [stack.enter_context(open(fname)) for fname in filenames]252    # Все открытые файлы будут автоматически закрыты в конце253    # оператор with, даже если попытки открыть файлы, идущие далее254    # в списке, вызывают исключение255```256257Каждый экземпляр поддерживает стек зарегистрированных колбэков, которые вызываются в обратном порядке при закрытии экземпляра (явно или неявно в конце оператора [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with)). Обратите внимание, что колбэки *не* вызываются неявно при сборке мусора экземпляра стека контекста.258259Такая стековая модель используется, чтобы менеджеры контекста, получающие свои ресурсы в методе `__init__` (например, файловые объекты), могли обрабатываться корректно.260261Поскольку зарегистрированные колбэки вызываются в порядке, обратном регистрации, в итоге это ведёт себя так, как если бы с зарегистрированным набором колбэков использовалось несколько вложенных операторов [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with). Это распространяется даже на обработку исключений: если внутренний колбэк подавляет или заменяет исключение, то внешним колбэкам будут переданы аргументы, основанные на этом обновлённом состоянии.262263Это относительно низкоуровневый API, который заботится о деталях корректной размотки стека колбэков выхода. Он предоставляет подходящую основу для менеджеров контекста более высокого уровня, которые манипулируют стеком выхода специфичным для приложения образом.264265Новое в версии 3.3.266267#### `enter_context(cm)`268269Входит в новый менеджер контекста и добавляет его метод [`__exit__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__exit__) в стек колбэков. Возвращаемое значение – результат собственного метода [`__enter__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__enter__) менеджера контекста.270271Эти менеджеры контекста могут подавлять исключения точно так же, как они это делали бы при непосредственном использовании в операторе [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with).272273#### `push(exit)`274275Добавляет метод [`__exit__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__exit__) менеджера контекста в стек колбэков.276277Поскольку `__enter__` *не* вызывается, этот метод можно использовать для покрытия части реализации [`__enter__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__enter__) собственным методом [`__exit__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__exit__) менеджера контекста.278279Если передан объект, не являющийся менеджером контекста, этот метод считает его колбэком с той же сигнатурой, что и метод [`__exit__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__exit__) менеджера контекста, и добавляет его напрямую в стек колбэков.280281Возвращая истинные значения, эти колбэки могут подавлять исключения так же, как это делают методы [`__exit__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__exit__) менеджеров контекста.282283Переданный объект возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.284285#### `callback(callback, *args, **kwds)`286287Принимает произвольную функцию-колбэк и аргументы и добавляет её в стек колбэков.288289В отличие от других методов, колбэки, добавленные таким образом, не могут подавлять исключения (поскольку им никогда не передаются сведения об исключении).290291Переданный колбэк возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.292293#### `pop_all()`294295Перемещает стек колбэков в новый экземпляр [`ExitStack`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ExitStack) и возвращает его. При этой операции колбэки не вызываются – вместо этого они будут вызваны при закрытии нового стека (явно или неявно в конце оператора [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with)).296297Например, группу файлов можно открыть как операцию «всё или ничего» следующим образом:298299```python300with ExitStack() as stack:301    files = [stack.enter_context(open(fname)) for fname in filenames]302    # Сохраните метод close, но пока не вызывайте его.303    close_files = stack.pop_all().close304    # Если открытие любого файла завершается неудачей, все ранее открытые файлы будут305    # закрыты автоматически. Если все файлы открыты успешно,306    # они останутся открытыми даже после завершения оператора with.307    # close_files() затем можно вызвать явно, чтобы закрыть их все.308```309310#### `close()`311312Немедленно разворачивает стек колбэков, вызывая колбэки в порядке, обратном регистрации. Для любых зарегистрированных менеджеров контекста и колбэков выхода переданные аргументы будут указывать, что исключения не произошло.313314## 29.6.2. Примеры и рецепты315316В этом разделе описаны некоторые примеры и рецепты для эффективного использования инструментов, предоставляемых [`contextlib`](https://python-all.ru/3.6/library/contextlib.html#module-contextlib).317318### 29.6.2.1. Поддержка переменного количества контекстных менеджеров319320Основной вариант использования [`ExitStack`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ExitStack) – это тот, что описан в документации класса: поддержка переменного числа менеджеров контекста и других операций очистки в одном операторе [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with). Переменность может быть обусловлена тем, что количество необходимых менеджеров контекста определяется пользовательским вводом (например, открытие указанной пользователем коллекции файлов), или тем, что некоторые менеджеры контекста являются необязательными:321322```python323with ExitStack() as stack:324    for resource in resources:325        stack.enter_context(resource)326    if need_special_resource():327        special = acquire_special_resource()328        stack.callback(release_special_resource, special)329    # Выполните операции, использующие полученные ресурсы330```331332Как показано, [`ExitStack`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ExitStack) также значительно упрощает использование операторов [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with) для управления произвольными ресурсами, которые изначально не поддерживают протокол управления контекстом.333334### 29.6.2.2. Упрощение поддержки одного необязательного контекстного менеджера335336В частном случае одного необязательного контекстного менеджера, [`ExitStack`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ExitStack) экземпляры можно использовать как «ничего не делающий» контекстный менеджер, что позволяет легко опустить контекстный менеджер, не затрагивая общую структуру исходного кода:337338```python339def debug_trace(details):340    if __debug__:341        return TraceContext(details)342    # В релизном режиме никаких особых действий с контекстом не выполняется343    return ExitStack()344345with debug_trace():346    # В режиме отладки набор трассируется, в остальных случаях работает обычным образом347```348349### 29.6.2.3. Перехват исключений из методов `__enter__`350351Иногда бывает желательно перехватывать исключения из реализации метода `__enter__`, *не* перехватывая случайно исключения из тела оператора [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with) или метода `__exit__` менеджера контекста. Используя [`ExitStack`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ExitStack), можно немного разделить шаги протокола управления контекстом, чтобы это стало возможным:352353```python354stack = ExitStack()355try:356    x = stack.enter_context(cm)357except Exception:358    # обработать исключение __enter__359else:360    with stack:361        # Обработка нормального случая362```363364Необходимость в этом, скорее всего, указывает на то, что базовый API должен предоставлять прямой интерфейс управления ресурсами для использования с операторами [`try`](https://python-all.ru/3.6/reference/compound_stmts.html#try)/[`except`](https://python-all.ru/3.6/reference/compound_stmts.html#except)/[`finally`](https://python-all.ru/3.6/reference/compound_stmts.html#finally), но не все API хорошо спроектированы в этом отношении. Когда менеджер контекста является единственным предоставляемым API управления ресурсами, тогда [`ExitStack`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ExitStack) может упростить обработку различных ситуаций, которые невозможно обработать напрямую в операторе [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with).365366### 29.6.2.4. Очистка в реализации `__enter__`367368Как отмечено в документации [`ExitStack.push()`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ExitStack.push), этот метод может быть полезен для очистки уже выделенного ресурса, если последующие шаги в реализации [`__enter__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__enter__) завершатся неудачей.369370Вот пример реализации этого для менеджера контекста, который принимает функции захвата и освобождения ресурса, а также опциональную функцию проверки, и отображает их на протокол управления контекстом:371372```python373from contextlib import contextmanager, AbstractContextManager, ExitStack374375class ResourceManager(AbstractContextManager):376377    def __init__(self, acquire_resource, release_resource, check_resource_ok=None):378        self.acquire_resource = acquire_resource379        self.release_resource = release_resource380        if check_resource_ok is None:381            def check_resource_ok(resource):382                return True383        self.check_resource_ok = check_resource_ok384385    @contextmanager386    def _cleanup_on_error(self):387        with ExitStack() as stack:388            stack.push(self)389            yield390            # Проверка прошла успешно и не вызвала исключение391            # Соответственно, мы хотим сохранить ресурс и передать его392            # обратно вызывающему коду393            stack.pop_all()394395    def __enter__(self):396        resource = self.acquire_resource()397        with self._cleanup_on_error():398            if not self.check_resource_ok(resource):399                msg = "Failed validation for {!r}"400                raise RuntimeError(msg.format(resource))401        return resource402403    def __exit__(self, *exc_details):404        # Нам не нужно дублировать логику освобождения ресурсов405        self.release_resource()406```407408### 29.6.2.5. Замена любого использования `try-finally` и флаговых переменных409410Иногда встречается шаблон, в котором используется инструкция `try-finally` с переменной-флагом, указывающей, должно ли выполняться тело предложения `finally`. В простейшей форме (если это нельзя обработать простым использованием предложения `except`) это выглядит примерно так:411412```python413cleanup_needed = True414try:415    result = perform_operation()416    if result:417        cleanup_needed = False418finally:419    if cleanup_needed:420        cleanup_resources()421```422423Как и в любом коде, основанном на инструкции `try`, это может вызвать проблемы при разработке и рецензировании, поскольку код настройки и код очистки могут оказаться разделенными произвольно длинными участками кода.424425[`ExitStack`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ExitStack) позволяет вместо этого зарегистрировать колбэк для выполнения в конце инструкции `with`, а затем позже решить пропустить выполнение этого колбэка:426427```python428from contextlib import ExitStack429430with ExitStack() as stack:431    stack.callback(cleanup_resources)432    result = perform_operation()433    if result:434        stack.pop_all()435```436437Это позволяет заранее явно указать предполагаемое поведение очистки, вместо того чтобы требовать отдельную переменную-флаг.438439Если конкретное приложение часто использует этот шаблон, его можно упростить еще больше с помощью небольшого вспомогательного класса:440441```python442from contextlib import ExitStack443444class Callback(ExitStack):445    def __init__(self, callback, *args, **kwds):446        super(Callback, self).__init__()447        self.callback(callback, *args, **kwds)448449    def cancel(self):450        self.pop_all()451452with Callback(cleanup_resources) as cb:453    result = perform_operation()454    if result:455        cb.cancel()456```457458Если очистка ресурсов уже не аккуратно оформлена в виде отдельной функции, то все равно можно использовать форму декоратора [`ExitStack.callback()`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ExitStack.callback), чтобы объявить очистку ресурсов заранее:459460```python461from contextlib import ExitStack462463with ExitStack() as stack:464    @stack.callback465    def cleanup_resources():466        ...467    result = perform_operation()468    if result:469        stack.pop_all()470```471472Из-за того, как работает протокол декоратора, функция обратного вызова, объявленная таким образом, не может принимать никаких параметров. Вместо этого любые ресурсы, которые необходимо освободить, должны быть доступны как переменные замыкания.473474### 29.6.2.6. Использование контекстного менеджера в качестве декоратора функции475476[`ContextDecorator`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ContextDecorator) позволяет использовать менеджер контекста как в обычной инструкции `with`, так и в качестве декоратора функции.477478Например, иногда полезно оборачивать функции или группы инструкций в логгер, который может отслеживать время входа и время выхода. Вместо написания как декоратора функции, так и менеджера контекста для этой задачи, наследование от [`ContextDecorator`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ContextDecorator) предоставляет обе возможности в одном определении:479480```python481from contextlib import ContextDecorator482import logging483484logging.basicConfig(level=logging.INFO)485486class track_entry_and_exit(ContextDecorator):487    def __init__(self, name):488        self.name = name489490    def __enter__(self):491        logging.info('Entering: %s', self.name)492493    def __exit__(self, exc_type, exc, exc_tb):494        logging.info('Exiting: %s', self.name)495```496497Экземпляры этого класса можно использовать как менеджер контекста:498499```python500with track_entry_and_exit('widget loader'):501    print('Some time consuming activity goes here')502    load_widget()503```504505А также как декоратор функции:506507```python508@track_entry_and_exit('widget loader')509def activity():510    print('Some time consuming activity goes here')511    load_widget()512```513514Обратите внимание, что есть одно дополнительное ограничение при использовании менеджеров контекста в качестве декораторов функций: невозможно получить доступ к возвращаемому значению [`__enter__()`](https://python-all.ru/3.6/reference/datamodel.html#object.__enter__). Если это значение необходимо, то все равно нужно использовать явную инструкцию `with`.515516> **См. также**517>518> **[**PEP 343**](https://python-all.ru/3.6/library/contextlib.html) – Оператор «with»**519>520> Спецификация, предыстория и примеры для инструкции [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with) в Python.521522## 29.6.3. Одноразовые, повторно используемые и реентерабельные контекстные менеджеры523524Большинство менеджеров контекста написаны таким образом, что их можно эффективно использовать только один раз в инструкции [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with). Эти одноразовые менеджеры контекста должны создаваться заново каждый раз при использовании – попытка использовать их второй раз вызовет исключение или иным образом приведет к некорректной работе.525526Это распространенное ограничение означает, что обычно рекомендуется создавать менеджеры контекста непосредственно в заголовке инструкции [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with), где они используются (как показано во всех примерах использования выше).527528Файлы являются примером эффективно одноразовых менеджеров контекста, поскольку первая инструкция [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with) закроет файл, предотвращая любые дальнейшие операции ввода-вывода с использованием этого объекта файла.529530Менеджеры контекста, созданные с помощью [`contextmanager()`](https://python-all.ru/3.6/library/contextlib.html#contextlib.contextmanager), также являются одноразовыми менеджерами контекста и будут сообщать об ошибке, если базовый генератор не сможет выполнить yield при попытке использовать их второй раз:531532```python533>>> from contextlib import contextmanager534>>> @contextmanager535... def singleuse():536...     print("Before")537...     yield538...     print("After")539...540>>> cm = singleuse()541>>> with cm:542...     pass543...544Before545After546>>> with cm:547...     pass548...549Traceback (most recent call last):550    ...551RuntimeError: generator didn't yield552```553554### 29.6.3.1. Реентерабельные контекстные менеджеры555556Более сложные менеджеры контекста могут быть «реентерабельными». Такие менеджеры контекста можно использовать не только в нескольких инструкциях [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with), но также *внутри* инструкции [`with`](https://python-all.ru/3.6/reference/compound_stmts.html#with), которая уже использует тот же менеджер контекста.557558[`threading.RLock`](https://python-all.ru/3.6/library/threading.html#threading.RLock) – это пример реентерабельного контекстного менеджера, как и [`suppress()`](https://python-all.ru/3.6/library/contextlib.html#contextlib.suppress) и [`redirect_stdout()`](https://python-all.ru/3.6/library/contextlib.html#contextlib.redirect_stdout). Вот очень простой пример реентерабельного использования:559560```python561>>> from contextlib import redirect_stdout562>>> from io import StringIO563>>> stream = StringIO()564>>> write_to_stream = redirect_stdout(stream)565>>> with write_to_stream:566...     print("This is written to the stream rather than stdout")567...     with write_to_stream:568...         print("This is also written to the stream")569...570>>> print("This is written directly to stdout")571This is written directly to stdout572>>> print(stream.getvalue())573This is written to the stream rather than stdout574This is also written to the stream575```576577Реальные примеры реентерабельности, скорее всего, будут включать несколько функций, вызывающих друг друга, и, следовательно, будут гораздо сложнее этого примера.578579Также обратите внимание, что быть реентерабельным – это *не* то же самое, что быть потокобезопасным. [`redirect_stdout()`](https://python-all.ru/3.6/library/contextlib.html#contextlib.redirect_stdout), например, определенно не является потокобезопасным, так как он вносит глобальное изменение в состояние системы, привязывая [`sys.stdout`](https://python-all.ru/3.6/library/sys.html#sys.stdout) к другому потоку данных.580581### 29.6.3.2. Повторно используемые контекстные менеджеры582583Отличающиеся от одноразовых и реентерабельных менеджеров контекста – «многократно используемые» менеджеры контекста (или, если быть совсем точным, «многократно используемые, но не реентерабельные» менеджеры контекста, поскольку реентерабельные менеджеры контекста также являются многократно используемыми). Эти менеджеры контекста поддерживают многократное использование, но завершатся ошибкой (или иным образом будут работать некорректно), если конкретный экземпляр менеджера контекста уже был использован во внешней инструкции with.584585[`threading.Lock`](https://python-all.ru/3.6/library/threading.html#threading.Lock) является примером многократно используемого, но не реентерабельного менеджера контекста (для реентерабельной блокировки необходимо использовать [`threading.RLock`](https://python-all.ru/3.6/library/threading.html#threading.RLock) вместо этого).586587Другим примером многократно используемого, но не реентерабельного менеджера контекста является [`ExitStack`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ExitStack), так как он вызывает *все* зарегистрированные в данный момент колбэки при выходе из любой инструкции with, независимо от того, где эти колбэки были добавлены:588589```python590>>> from contextlib import ExitStack591>>> stack = ExitStack()592>>> with stack:593...     stack.callback(print, "Callback: from first context")594...     print("Leaving first context")595...596Leaving first context597Callback: from first context598>>> with stack:599...     stack.callback(print, "Callback: from second context")600...     print("Leaving second context")601...602Leaving second context603Callback: from second context604>>> with stack:605...     stack.callback(print, "Callback: from outer context")606...     with stack:607...         stack.callback(print, "Callback: from inner context")608...         print("Leaving inner context")609...     print("Leaving outer context")610...611Leaving inner context612Callback: from inner context613Callback: from outer context614Leaving outer context615```616617Как показывает вывод примера, повторное использование одного объекта стека в нескольких инструкциях with работает корректно, но попытка вложить их приведет к очистке стека в конце самой внутренней инструкции with, что вряд ли является желательным поведением.618619Использование отдельных экземпляров [`ExitStack`](https://python-all.ru/3.6/library/contextlib.html#contextlib.ExitStack) вместо повторного использования одного экземпляра позволяет избежать этой проблемы:620621```python622>>> from contextlib import ExitStack623>>> with ExitStack() as outer_stack:624...     outer_stack.callback(print, "Callback: from outer context")625...     with ExitStack() as inner_stack:626...         inner_stack.callback(print, "Callback: from inner context")627...         print("Leaving inner context")628...     print("Leaving outer context")629...630Leaving inner context631Callback: from inner context632Leaving outer context633Callback: from outer context634```635