contextlib.md
1> **Источник:** https://python-all.ru/3.5/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.5/library/contextlib.html#module-contextlib) – Утилиты для контекстов оператора [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with)89**Исходный код:** [Lib/contextlib.py](https://python-all.ru/src/3.5/Lib/contextlib.py)1011---1213Этот модуль предоставляет утилиты для типовых задач, связанных с оператором [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with). Для получения дополнительной информации см. также [Типы менеджеров контекста](https://python-all.ru/3.5/library/stdtypes.html#typecontextmanager) и [Менеджеры контекста оператора with](https://python-all.ru/3.5/reference/datamodel.html#context-managers).1415## 29.6.1. Утилиты1617Предоставляемые функции и классы:1819#### `@contextlib.contextmanager`2021Эта функция – [декоратор](https://python-all.ru/3.5/glossary.html#term-decorator), который можно использовать для определения фабричной функции для контекстных менеджеров оператора [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with), без необходимости создавать класс или отдельные методы [`__enter__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__enter__) и [`__exit__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__exit__).2223Простой пример (это не рекомендуется как реальный способ генерации HTML!):2425```python26from contextlib import contextmanager2728@contextmanager29def tag(name):30 print("<%s>" % name)31 yield32 print("</%s>" % name)3334>>> with tag("h1"):35... print("foo")36...37<h1>38foo39</h1>40```4142Декорируемая функция при вызове должна возвращать [генератор](https://python-all.ru/3.5/glossary.html#term-generator)-итератор. Этот итератор должен возвращать ровно одно значение, которое будет связано с целями в предложении [`as`](https://python-all.ru/3.5/reference/compound_stmts.html#as) оператора [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with), если таковые имеются.4344В момент, где генератор делает yield, выполняется блок, вложенный в оператор [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with). Затем генератор возобновляется после выхода из блока. Если в блоке возникает необработанное исключение, оно повторно возбуждается внутри генератора в точке, где произошел yield. Таким образом, можно использовать оператор [`try`](https://python-all.ru/3.5/reference/compound_stmts.html#try)…[`except`](https://python-all.ru/3.5/reference/compound_stmts.html#except)…[`finally`](https://python-all.ru/3.5/reference/compound_stmts.html#finally) для перехвата ошибки (если есть) или для выполнения очистки. Если исключение перехватывается только для того, чтобы зарегистрировать его или выполнить какое-либо действие (а не подавить полностью), генератор должен повторно возбудить это исключение. В противном случае контекстный менеджер генератора сообщит оператору [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with), что исключение обработано, и выполнение продолжится с оператора, следующего сразу за оператором [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with).4546[`contextmanager()`](https://python-all.ru/3.5/library/contextlib.html#contextlib.contextmanager) использует [`ContextDecorator`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ContextDecorator), поэтому создаваемые им контекстные менеджеры могут использоваться как в качестве декораторов, так и в операторах [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with). При использовании в качестве декоратора для каждого вызова функции неявно создается новый экземпляр генератора (это позволяет в противном случае «одноразовым» контекстным менеджерам, созданным с помощью [`contextmanager()`](https://python-all.ru/3.5/library/contextlib.html#contextlib.contextmanager), соответствовать требованию, согласно которому контекстные менеджеры должны поддерживать многократные вызовы, чтобы их можно было использовать в качестве декораторов).4748Изменено в версии 3.2: Использование [`ContextDecorator`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ContextDecorator).4950#### `contextlib.closing(thing)`5152Возвращает контекстный менеджер, который закрывает *thing* после завершения блока. По сути эквивалентно следующему:5354```python55from contextlib import contextmanager5657@contextmanager58def closing(thing):59 try:60 yield thing61 finally:62 thing.close()63```6465И позволяет писать код следующим образом:6667```python68from contextlib import closing69from urllib.request import urlopen7071with closing(urlopen('http://www.python.org')) as page:72 for line in page:73 print(line)74```7576без необходимости явно закрывать `page`. Даже если произойдет ошибка, `page.close()` будет вызван при выходе из блока [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with).7778#### `contextlib.suppress(*exceptions)`7980Возвращает менеджер контекста, который подавляет любые из указанных исключений, если они возникают в теле оператора with, а затем возобновляет выполнение с первого оператора после конца оператора with.8182Как и любой другой механизм, полностью подавляющий исключения, этот менеджер контекста следует использовать только для обработки очень специфических ошибок, когда молчаливое продолжение выполнения программы является правильным решением.8384Например:8586```python87from contextlib import suppress8889with suppress(FileNotFoundError):90 os.remove('somefile.tmp')9192with suppress(FileNotFoundError):93 os.remove('someotherfile.tmp')94```9596Этот код эквивалентен:9798```python99try:100 os.remove('somefile.tmp')101except FileNotFoundError:102 pass103104try:105 os.remove('someotherfile.tmp')106except FileNotFoundError:107 pass108```109110Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.5/library/contextlib.html#reentrant-cms).111112Новое в версии 3.4.113114#### `contextlib.redirect_stdout(new_target)`115116Менеджер контекста для временного перенаправления [`sys.stdout`](https://python-all.ru/3.5/library/sys.html#sys.stdout) в другой файл или файлоподобный объект.117118Этот инструмент добавляет гибкость существующим функциям или классам, чей вывод жестко привязан к stdout.119120Например, вывод [`help()`](https://python-all.ru/3.5/library/functions.html#help) обычно направляется в *sys.stdout*. Вы можете захватить этот вывод в строку, перенаправив вывод в [`io.StringIO`](https://python-all.ru/3.5/library/io.html#io.StringIO) объект:121122```python123f = io.StringIO()124with redirect_stdout(f):125 help(pow)126s = f.getvalue()127```128129Чтобы отправить вывод [`help()`](https://python-all.ru/3.5/library/functions.html#help) в файл на диске, перенаправьте вывод в обычный файл:130131```python132with open('help.txt', 'w') as f:133 with redirect_stdout(f):134 help(pow)135```136137Чтобы отправить вывод [`help()`](https://python-all.ru/3.5/library/functions.html#help) в *sys.stderr*:138139```python140with redirect_stdout(sys.stderr):141 help(pow)142```143144Обратите внимание, что глобальный побочный эффект на [`sys.stdout`](https://python-all.ru/3.5/library/sys.html#sys.stdout) означает, что этот менеджер контекста не подходит для использования в библиотечном коде и большинстве многопоточных приложений. Он также не влияет на вывод подпроцессов. Тем не менее, это всё ещё полезный подход для многих вспомогательных скриптов.145146Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.5/library/contextlib.html#reentrant-cms).147148Новое в версии 3.4.149150#### `contextlib.redirect_stderr(new_target)`151152Аналогично [`redirect_stdout()`](https://python-all.ru/3.5/library/contextlib.html#contextlib.redirect_stdout), но перенаправляет [`sys.stderr`](https://python-all.ru/3.5/library/sys.html#sys.stderr) в другой файл или файлоподобный объект.153154Этот менеджер контекста является [реентерабельным](https://python-all.ru/3.5/library/contextlib.html#reentrant-cms).155156Новое в версии 3.5.157158#### `class contextlib.ContextDecorator`159160Базовый класс, который позволяет менеджеру контекста также использоваться в качестве декоратора.161162Менеджеры контекста, наследующие от `ContextDecorator`, должны обычным образом реализовывать `__enter__` и `__exit__`. `__exit__` сохраняет необязательную обработку исключений даже при использовании в качестве декоратора.163164`ContextDecorator` используется [`contextmanager()`](https://python-all.ru/3.5/library/contextlib.html#contextlib.contextmanager), поэтому эта функциональность доступна автоматически.165166Пример `ContextDecorator`:167168```python169from contextlib import ContextDecorator170171class mycontext(ContextDecorator):172 def __enter__(self):173 print('Starting')174 return self175176 def __exit__(self, *exc):177 print('Finishing')178 return False179180>>> @mycontext()181... def function():182... print('The bit in the middle')183...184>>> function()185Starting186The bit in the middle187Finishing188189>>> with mycontext():190... print('The bit in the middle')191...192Starting193The bit in the middle194Finishing195```196197Это изменение – всего лишь синтаксический сахар для любой конструкции следующего вида:198199```python200def f():201 with cm():202 # Выполнить действия203```204205`ContextDecorator` позволяет вместо этого писать:206207```python208@cm()209def f():210 # Выполнить действия211```212213Становится ясно, что `cm` применяется ко всей функции, а не только к её части (и экономия уровня отступа тоже приятна).214215Существующие менеджеры контекста, уже имеющие базовый класс, можно расширить, используя `ContextDecorator` в качестве примеси (mixin):216217```python218from contextlib import ContextDecorator219220class mycontext(ContextBaseClass, ContextDecorator):221 def __enter__(self):222 return self223224 def __exit__(self, *exc):225 return False226```227228> **Примечание**229>230> Поскольку декорированная функция должна допускать многократный вызов, нижележащий менеджер контекста должен поддерживать использование в нескольких операторах [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with). Если это не так, следует использовать исходную конструкцию с явным оператором [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with) внутри функции.231232Новое в версии 3.2.233234#### `class contextlib.ExitStack`235236Менеджер контекста, предназначенный для упрощения программного комбинирования других менеджеров контекста и функций очистки, особенно тех, которые являются необязательными или определяются входными данными.237238Например, набор файлов можно легко обработать в одном операторе with следующим образом:239240```python241with ExitStack() as stack:242 files = [stack.enter_context(open(fname)) for fname in filenames]243 # Все открытые файлы будут автоматически закрыты в конце244 # оператор with, даже если попытки открыть файлы, идущие далее245 # в списке, вызывают исключение246```247248Каждый экземпляр поддерживает стек зарегистрированных колбэков, которые вызываются в обратном порядке при закрытии экземпляра (явно или неявно в конце оператора [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with)). Обратите внимание, что колбэки *не* вызываются неявно при сборке мусора экземпляра стека контекста.249250Такая стековая модель используется, чтобы менеджеры контекста, получающие свои ресурсы в методе `__init__` (например, файловые объекты), могли обрабатываться корректно.251252Поскольку зарегистрированные колбэки вызываются в порядке, обратном регистрации, в итоге это ведёт себя так, как если бы с зарегистрированным набором колбэков использовалось несколько вложенных операторов [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with). Это распространяется даже на обработку исключений: если внутренний колбэк подавляет или заменяет исключение, то внешним колбэкам будут переданы аргументы, основанные на этом обновлённом состоянии.253254Это относительно низкоуровневый API, который заботится о деталях корректной размотки стека колбэков выхода. Он предоставляет подходящую основу для менеджеров контекста более высокого уровня, которые манипулируют стеком выхода специфичным для приложения образом.255256Новое в версии 3.3.257258#### `enter_context(cm)`259260Входит в новый менеджер контекста и добавляет его метод [`__exit__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__exit__) в стек колбэков. Возвращаемое значение – результат собственного метода [`__enter__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__enter__) менеджера контекста.261262Эти менеджеры контекста могут подавлять исключения точно так же, как они это делали бы при непосредственном использовании в операторе [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with).263264#### `push(exit)`265266Добавляет метод [`__exit__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__exit__) менеджера контекста в стек колбэков.267268Поскольку `__enter__` *не* вызывается, этот метод можно использовать для покрытия части реализации [`__enter__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__enter__) собственным методом [`__exit__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__exit__) менеджера контекста.269270Если передан объект, не являющийся менеджером контекста, этот метод считает его колбэком с той же сигнатурой, что и метод [`__exit__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__exit__) менеджера контекста, и добавляет его напрямую в стек колбэков.271272Возвращая истинные значения, эти колбэки могут подавлять исключения так же, как это делают методы [`__exit__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__exit__) менеджеров контекста.273274Переданный объект возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.275276#### `callback(callback, *args, **kwds)`277278Принимает произвольную функцию-колбэк и аргументы и добавляет её в стек колбэков.279280В отличие от других методов, колбэки, добавленные таким образом, не могут подавлять исключения (поскольку им никогда не передаются сведения об исключении).281282Переданный колбэк возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.283284#### `pop_all()`285286Перемещает стек колбэков в новый экземпляр [`ExitStack`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ExitStack) и возвращает его. При этой операции колбэки не вызываются – вместо этого они будут вызваны при закрытии нового стека (явно или неявно в конце оператора [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with)).287288Например, группу файлов можно открыть как операцию «всё или ничего» следующим образом:289290```python291with ExitStack() as stack:292 files = [stack.enter_context(open(fname)) for fname in filenames]293 # Сохраните метод close, но пока не вызывайте его.294 close_files = stack.pop_all().close295 # Если открытие любого файла завершается неудачей, все ранее открытые файлы будут296 # закрыты автоматически. Если все файлы открыты успешно,297 # они останутся открытыми даже после завершения оператора with.298 # close_files() затем можно вызвать явно, чтобы закрыть их все.299```300301#### `close()`302303Немедленно разворачивает стек колбэков, вызывая колбэки в порядке, обратном регистрации. Для любых зарегистрированных менеджеров контекста и колбэков выхода переданные аргументы будут указывать, что исключения не произошло.304305## 29.6.2. Примеры и рецепты306307В этом разделе описаны некоторые примеры и рецепты для эффективного использования инструментов, предоставляемых [`contextlib`](https://python-all.ru/3.5/library/contextlib.html#module-contextlib).308309### 29.6.2.1. Поддержка переменного числа менеджеров контекста310311Основной вариант использования [`ExitStack`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ExitStack) – это тот, что описан в документации класса: поддержка переменного числа менеджеров контекста и других операций очистки в одном операторе [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with). Переменность может быть обусловлена тем, что количество необходимых менеджеров контекста определяется пользовательским вводом (например, открытие указанной пользователем коллекции файлов), или тем, что некоторые менеджеры контекста являются необязательными:312313```python314with ExitStack() as stack:315 for resource in resources:316 stack.enter_context(resource)317 if need_special_resource():318 special = acquire_special_resource()319 stack.callback(release_special_resource, special)320 # Выполните операции, использующие полученные ресурсы321```322323Как показано, [`ExitStack`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ExitStack) также значительно упрощает использование операторов [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with) для управления произвольными ресурсами, которые изначально не поддерживают протокол управления контекстом.324325### 29.6.2.2. Упрощение поддержки одного опционального менеджера контекста326327В частном случае одного необязательного контекстного менеджера, [`ExitStack`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ExitStack) экземпляры можно использовать как «ничего не делающий» контекстный менеджер, что позволяет легко опустить контекстный менеджер, не затрагивая общую структуру исходного кода:328329```python330def debug_trace(details):331 if __debug__:332 return TraceContext(details)333 # В релизном режиме никаких особых действий с контекстом не выполняется334 return ExitStack()335336with debug_trace():337 # В режиме отладки набор трассируется, в остальных случаях работает обычным образом338```339340### 29.6.2.3. Перехват исключений из методов `__enter__`341342Иногда бывает желательно перехватывать исключения из реализации метода `__enter__`, *не* перехватывая случайно исключения из тела оператора [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with) или метода `__exit__` менеджера контекста. Используя [`ExitStack`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ExitStack), можно немного разделить шаги протокола управления контекстом, чтобы это стало возможным:343344```python345stack = ExitStack()346try:347 x = stack.enter_context(cm)348except Exception:349 # обработать исключение __enter__350else:351 with stack:352 # Обработка нормального случая353```354355Необходимость в этом, скорее всего, указывает на то, что базовый API должен предоставлять прямой интерфейс управления ресурсами для использования с операторами [`try`](https://python-all.ru/3.5/reference/compound_stmts.html#try)/[`except`](https://python-all.ru/3.5/reference/compound_stmts.html#except)/[`finally`](https://python-all.ru/3.5/reference/compound_stmts.html#finally), но не все API хорошо спроектированы в этом отношении. Когда менеджер контекста является единственным предоставляемым API управления ресурсами, тогда [`ExitStack`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ExitStack) может упростить обработку различных ситуаций, которые невозможно обработать напрямую в операторе [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with).356357### 29.6.2.4. Очистка в реализации `__enter__`358359Как отмечено в документации [`ExitStack.push()`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ExitStack.push), этот метод может быть полезен для очистки уже выделенного ресурса, если последующие шаги в реализации [`__enter__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__enter__) завершатся неудачей.360361Вот пример реализации этого для менеджера контекста, который принимает функции захвата и освобождения ресурса, а также опциональную функцию проверки, и отображает их на протокол управления контекстом:362363```python364from contextlib import contextmanager, ExitStack365366class ResourceManager:367368 def __init__(self, acquire_resource, release_resource, check_resource_ok=None):369 self.acquire_resource = acquire_resource370 self.release_resource = release_resource371 if check_resource_ok is None:372 def check_resource_ok(resource):373 return True374 self.check_resource_ok = check_resource_ok375376 @contextmanager377 def _cleanup_on_error(self):378 with ExitStack() as stack:379 stack.push(self)380 yield381 # Проверка прошла успешно и не вызвала исключение382 # Соответственно, мы хотим сохранить ресурс и передать его383 # обратно вызывающему коду384 stack.pop_all()385386 def __enter__(self):387 resource = self.acquire_resource()388 with self._cleanup_on_error():389 if not self.check_resource_ok(resource):390 msg = "Failed validation for {!r}"391 raise RuntimeError(msg.format(resource))392 return resource393394 def __exit__(self, *exc_details):395 # Нам не нужно дублировать логику освобождения ресурсов396 self.release_resource()397```398399### 29.6.2.5. Замена любого использования `try-finally` и флаговых переменных400401Иногда встречается шаблон, в котором используется инструкция `try-finally` с переменной-флагом, указывающей, должно ли выполняться тело предложения `finally`. В простейшей форме (если это нельзя обработать простым использованием предложения `except`) это выглядит примерно так:402403```python404cleanup_needed = True405try:406 result = perform_operation()407 if result:408 cleanup_needed = False409finally:410 if cleanup_needed:411 cleanup_resources()412```413414Как и в любом коде, основанном на инструкции `try`, это может вызвать проблемы при разработке и рецензировании, поскольку код настройки и код очистки могут оказаться разделенными произвольно длинными участками кода.415416[`ExitStack`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ExitStack) позволяет вместо этого зарегистрировать колбэк для выполнения в конце инструкции `with`, а затем позже решить пропустить выполнение этого колбэка:417418```python419from contextlib import ExitStack420421with ExitStack() as stack:422 stack.callback(cleanup_resources)423 result = perform_operation()424 if result:425 stack.pop_all()426```427428Это позволяет заранее явно указать предполагаемое поведение очистки, вместо того чтобы требовать отдельную переменную-флаг.429430Если конкретное приложение часто использует этот шаблон, его можно упростить еще больше с помощью небольшого вспомогательного класса:431432```python433from contextlib import ExitStack434435class Callback(ExitStack):436 def __init__(self, callback, *args, **kwds):437 super(Callback, self).__init__()438 self.callback(callback, *args, **kwds)439440 def cancel(self):441 self.pop_all()442443with Callback(cleanup_resources) as cb:444 result = perform_operation()445 if result:446 cb.cancel()447```448449Если очистка ресурсов уже не аккуратно оформлена в виде отдельной функции, то все равно можно использовать форму декоратора [`ExitStack.callback()`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ExitStack.callback), чтобы объявить очистку ресурсов заранее:450451```python452from contextlib import ExitStack453454with ExitStack() as stack:455 @stack.callback456 def cleanup_resources():457 ...458 result = perform_operation()459 if result:460 stack.pop_all()461```462463Из-за того, как работает протокол декоратора, функция обратного вызова, объявленная таким образом, не может принимать никаких параметров. Вместо этого любые ресурсы, которые необходимо освободить, должны быть доступны как переменные замыкания.464465### 29.6.2.6. Использование менеджера контекста в качестве декоратора функции466467[`ContextDecorator`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ContextDecorator) позволяет использовать менеджер контекста как в обычной инструкции `with`, так и в качестве декоратора функции.468469Например, иногда полезно оборачивать функции или группы инструкций в логгер, который может отслеживать время входа и время выхода. Вместо написания как декоратора функции, так и менеджера контекста для этой задачи, наследование от [`ContextDecorator`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ContextDecorator) предоставляет обе возможности в одном определении:470471```python472from contextlib import ContextDecorator473import logging474475logging.basicConfig(level=logging.INFO)476477class track_entry_and_exit(ContextDecorator):478 def __init__(self, name):479 self.name = name480481 def __enter__(self):482 logging.info('Entering: {}'.format(self.name))483484 def __exit__(self, exc_type, exc, exc_tb):485 logging.info('Exiting: {}'.format(self.name))486```487488Экземпляры этого класса можно использовать как менеджер контекста:489490```python491with track_entry_and_exit('widget loader'):492 print('Some time consuming activity goes here')493 load_widget()494```495496А также как декоратор функции:497498```python499@track_entry_and_exit('widget loader')500def activity():501 print('Some time consuming activity goes here')502 load_widget()503```504505Обратите внимание, что есть одно дополнительное ограничение при использовании менеджеров контекста в качестве декораторов функций: невозможно получить доступ к возвращаемому значению [`__enter__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__enter__). Если это значение необходимо, то все равно нужно использовать явную инструкцию `with`.506507> **См. также**508>509> **[**PEP 343**](https://python-all.ru/3.5/library/contextlib.html) – Оператор «with»**510>511> Спецификация, предыстория и примеры для инструкции512>513> [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with)514>515> в Python.516517## 29.6.3. Одноразовые, повторно используемые и реентерабельные менеджеры контекста518519Большинство менеджеров контекста написаны таким образом, что их можно эффективно использовать только один раз в инструкции [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with). Эти одноразовые менеджеры контекста должны создаваться заново каждый раз при использовании – попытка использовать их второй раз вызовет исключение или иным образом приведет к некорректной работе.520521Это распространенное ограничение означает, что обычно рекомендуется создавать менеджеры контекста непосредственно в заголовке инструкции [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with), где они используются (как показано во всех примерах использования выше).522523Файлы являются примером эффективно одноразовых менеджеров контекста, поскольку первая инструкция [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with) закроет файл, предотвращая любые дальнейшие операции ввода-вывода с использованием этого объекта файла.524525Менеджеры контекста, созданные с помощью [`contextmanager()`](https://python-all.ru/3.5/library/contextlib.html#contextlib.contextmanager), также являются одноразовыми менеджерами контекста и будут сообщать об ошибке, если базовый генератор не сможет выполнить yield при попытке использовать их второй раз:526527```python528>>> from contextlib import contextmanager529>>> @contextmanager530... def singleuse():531... print("Before")532... yield533... print("After")534...535>>> cm = singleuse()536>>> with cm:537... pass538...539Before540After541>>> with cm:542... pass543...544Traceback (most recent call last):545 ...546RuntimeError: generator didn't yield547```548549### 29.6.3.1. Реентерабельные менеджеры контекста550551Более сложные менеджеры контекста могут быть «реентерабельными». Такие менеджеры контекста можно использовать не только в нескольких инструкциях [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with), но также *внутри* инструкции [`with`](https://python-all.ru/3.5/reference/compound_stmts.html#with), которая уже использует тот же менеджер контекста.552553[`threading.RLock`](https://python-all.ru/3.5/library/threading.html#threading.RLock) – это пример реентерабельного контекстного менеджера, как и [`suppress()`](https://python-all.ru/3.5/library/contextlib.html#contextlib.suppress) и [`redirect_stdout()`](https://python-all.ru/3.5/library/contextlib.html#contextlib.redirect_stdout). Вот очень простой пример реентерабельного использования:554555```python556>>> from contextlib import redirect_stdout557>>> from io import StringIO558>>> stream = StringIO()559>>> write_to_stream = redirect_stdout(stream)560>>> with write_to_stream:561... print("This is written to the stream rather than stdout")562... with write_to_stream:563... print("This is also written to the stream")564...565>>> print("This is written directly to stdout")566This is written directly to stdout567>>> print(stream.getvalue())568This is written to the stream rather than stdout569This is also written to the stream570```571572Реальные примеры реентерабельности, скорее всего, будут включать несколько функций, вызывающих друг друга, и, следовательно, будут гораздо сложнее этого примера.573574Также обратите внимание, что быть реентерабельным – это *не* то же самое, что быть потокобезопасным. [`redirect_stdout()`](https://python-all.ru/3.5/library/contextlib.html#contextlib.redirect_stdout), например, определенно не является потокобезопасным, так как он вносит глобальное изменение в состояние системы, привязывая [`sys.stdout`](https://python-all.ru/3.5/library/sys.html#sys.stdout) к другому потоку данных.575576### 29.6.3.2. Повторно используемые менеджеры контекста577578Отличающиеся от одноразовых и реентерабельных менеджеров контекста – «многократно используемые» менеджеры контекста (или, если быть совсем точным, «многократно используемые, но не реентерабельные» менеджеры контекста, поскольку реентерабельные менеджеры контекста также являются многократно используемыми). Эти менеджеры контекста поддерживают многократное использование, но завершатся ошибкой (или иным образом будут работать некорректно), если конкретный экземпляр менеджера контекста уже был использован во внешней инструкции with.579580[`threading.Lock`](https://python-all.ru/3.5/library/threading.html#threading.Lock) является примером многократно используемого, но не реентерабельного менеджера контекста (для реентерабельной блокировки необходимо использовать [`threading.RLock`](https://python-all.ru/3.5/library/threading.html#threading.RLock) вместо этого).581582Другим примером многократно используемого, но не реентерабельного менеджера контекста является [`ExitStack`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ExitStack), так как он вызывает *все* зарегистрированные в данный момент колбэки при выходе из любой инструкции with, независимо от того, где эти колбэки были добавлены:583584```python585>>> from contextlib import ExitStack586>>> stack = ExitStack()587>>> with stack:588... stack.callback(print, "Callback: from first context")589... print("Leaving first context")590...591Leaving first context592Callback: from first context593>>> with stack:594... stack.callback(print, "Callback: from second context")595... print("Leaving second context")596...597Leaving second context598Callback: from second context599>>> with stack:600... stack.callback(print, "Callback: from outer context")601... with stack:602... stack.callback(print, "Callback: from inner context")603... print("Leaving inner context")604... print("Leaving outer context")605...606Leaving inner context607Callback: from inner context608Callback: from outer context609Leaving outer context610```611612Как показывает вывод примера, повторное использование одного объекта стека в нескольких инструкциях with работает корректно, но попытка вложить их приведет к очистке стека в конце самой внутренней инструкции with, что вряд ли является желательным поведением.613614Использование отдельных экземпляров [`ExitStack`](https://python-all.ru/3.5/library/contextlib.html#contextlib.ExitStack) вместо повторного использования одного экземпляра позволяет избежать этой проблемы:615616```python617>>> from contextlib import ExitStack618>>> with ExitStack() as outer_stack:619... outer_stack.callback(print, "Callback: from outer context")620... with ExitStack() as inner_stack:621... inner_stack.callback(print, "Callback: from inner context")622... print("Leaving inner context")623... print("Leaving outer context")624...625Leaving inner context626Callback: from inner context627Leaving outer context628Callback: from outer context629```630