contextlib.md
1> **Источник:** https://python-all.ru/3.4/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.4/library/contextlib.html#module-contextlib) – Утилиты для контекстов [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with)-оператора89**Исходный код:** [Lib/contextlib.py](https://python-all.ru/src/3.4/Lib/contextlib.py)1011---1213Этот модуль предоставляет утилиты для типичных задач, связанных с оператором [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with). Дополнительную информацию см. также в разделах [*Типы менеджеров контекста*](https://python-all.ru/3.4/library/stdtypes.html#typecontextmanager) и [*Менеджеры контекста оператора with*](https://python-all.ru/3.4/reference/datamodel.html#context-managers).1415## 29.6.1. Утилиты1617Предоставляемые функции и классы:1819#### `@contextlib.contextmanager`2021Эта функция – [*декоратор*](https://python-all.ru/3.4/glossary.html#term-decorator), который можно использовать для определения фабричной функции для менеджеров контекста оператора [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with), без необходимости создавать класс или отдельные методы [`__enter__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__enter__) и [`__exit__()`](https://python-all.ru/3.4/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.4/glossary.html#term-generator)-итератор. Этот итератор должен передать (yield) ровно одно значение, которое будет связано с целями в предложении [`as`](https://python-all.ru/3.4/reference/compound_stmts.html#as) оператора [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with), если оно есть.4344В точке, где генератор выполняет yield, выполняется блок, вложенный в оператор [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with). Затем генератор возобновляется после выхода из блока. Если в блоке возникает необработанное исключение, оно повторно возбуждается внутри генератора в точке, где произошёл yield. Таким образом, можно использовать конструкцию [`try`](https://python-all.ru/3.4/reference/compound_stmts.html#try)...[`except`](https://python-all.ru/3.4/reference/compound_stmts.html#except)...[`finally`](https://python-all.ru/3.4/reference/compound_stmts.html#finally) для перехвата ошибки (если она есть) или для обеспечения выполнения очистки. Если исключение перехватывается лишь для того, чтобы залогировать его или выполнить какое-либо действие (а не подавить его полностью), генератор должен возобновить (reraise) это исключение. В противном случае менеджер контекста-генератор сообщит оператору [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with), что исключение обработано, и выполнение продолжится с оператора, непосредственно следующего за оператором [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with).4546[`contextmanager()`](https://python-all.ru/3.4/library/contextlib.html#contextlib.contextmanager) использует [`ContextDecorator`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ContextDecorator), поэтому создаваемые им менеджеры контекста можно использовать как в качестве декораторов, так и в операторах [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with). При использовании в качестве декоратора новый экземпляр генератора неявно создаётся при каждом вызове функции (это позволяет менеджерам контекста, созданным [`contextmanager()`](https://python-all.ru/3.4/library/contextlib.html#contextlib.contextmanager), которые в противном случае были бы «одноразовыми», удовлетворять требованию, что менеджеры контекста должны поддерживать множественные вызовы, чтобы использоваться в качестве декораторов).4748Изменено в версии 3.2: Использование [`ContextDecorator`](https://python-all.ru/3.4/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.4/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.4/library/contextlib.html#reentrant-cms).111112Новое в версии 3.4.113114#### `contextlib.redirect_stdout(new_target)`115116Менеджер контекста для временного перенаправления [`sys.stdout`](https://python-all.ru/3.4/library/sys.html#sys.stdout) в другой файл или файлоподобный объект.117118Этот инструмент добавляет гибкость существующим функциям или классам, чей вывод жестко привязан к stdout.119120Например, вывод [`help()`](https://python-all.ru/3.4/library/functions.html#help) обычно отправляется в *sys.stdout*. Можно захватить этот вывод в строку, перенаправив его в объект [`io.StringIO`](https://python-all.ru/3.4/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.4/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.4/library/functions.html#help) в *sys.stderr*:138139```python140with redirect_stdout(sys.stderr):141 help(pow)142```143144Обратите внимание, что глобальный побочный эффект на [`sys.stdout`](https://python-all.ru/3.4/library/sys.html#sys.stdout) означает, что этот менеджер контекста не подходит для использования в библиотечном коде и в большинстве многопоточных приложений. Он также не влияет на вывод подпроцессов. Тем не менее, он остаётся полезным подходом для многих служебных скриптов.145146Этот менеджер контекста является [*реентерабельным*](https://python-all.ru/3.4/library/contextlib.html#reentrant-cms).147148Новое в версии 3.4.149150#### `class contextlib.ContextDecorator`151152Базовый класс, который позволяет менеджеру контекста также использоваться в качестве декоратора.153154Менеджеры контекста, наследующие от `ContextDecorator`, должны реализовывать `__enter__` и `__exit__` как обычно. `__exit__` сохраняет свою опциональную обработку исключений даже при использовании в качестве декоратора.155156`ContextDecorator` используется [`contextmanager()`](https://python-all.ru/3.4/library/contextlib.html#contextlib.contextmanager), поэтому эта функциональность доступна автоматически.157158Пример `ContextDecorator`:159160```python161from contextlib import ContextDecorator162163class mycontext(ContextDecorator):164 def __enter__(self):165 print('Starting')166 return self167168 def __exit__(self, *exc):169 print('Finishing')170 return False171172>>> @mycontext()173... def function():174... print('The bit in the middle')175...176>>> function()177Starting178The bit in the middle179Finishing180181>>> with mycontext():182... print('The bit in the middle')183...184Starting185The bit in the middle186Finishing187```188189Это изменение – всего лишь синтаксический сахар для любой конструкции следующего вида:190191```python192def f():193 with cm():194 # Выполнить действия195```196197`ContextDecorator` позволяет вместо этого написать:198199```python200@cm()201def f():202 # Выполнить действия203```204205Это даёт понять, что `cm` применяется ко всей функции, а не только к её части (и экономия уровня отступа тоже приятна).206207Существующие менеджеры контекста, у которых уже есть базовый класс, могут быть расширены путём использования `ContextDecorator` в качестве примесного класса (mixin class):208209```python210from contextlib import ContextDecorator211212class mycontext(ContextBaseClass, ContextDecorator):213 def __enter__(self):214 return self215216 def __exit__(self, *exc):217 return False218```219220> **Примечание**221>222> Поскольку декорированная функция должна допускать многократный вызов, базовый менеджер контекста должен поддерживать использование в нескольких операторах [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with). Если это не так, то следует использовать исходную конструкцию с явным оператором [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with) внутри функции.223224Новое в версии 3.2.225226#### `class contextlib.ExitStack`227228Менеджер контекста, предназначенный для упрощения программного комбинирования других менеджеров контекста и функций очистки, особенно тех, которые являются необязательными или определяются входными данными.229230Например, набор файлов можно легко обработать в одном операторе with следующим образом:231232```python233with ExitStack() as stack:234 files = [stack.enter_context(open(fname)) for fname in filenames]235 # Все открытые файлы будут автоматически закрыты в конце236 # оператор with, даже если попытки открыть файлы, идущие далее237 # в списке, вызывают исключение238```239240Каждый экземпляр поддерживает стек зарегистрированных колбэков, которые вызываются в обратном порядке при закрытии экземпляра (явно или неявно в конце оператора [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with)). Обратите внимание, что колбэки *не* вызываются неявно при сборке мусора экземпляра стека контекстов.241242Эта модель стека используется, чтобы менеджеры контекста, которые получают свои ресурсы в методе `__init__` (например, файловые объекты), могли быть обработаны корректно.243244Поскольку зарегистрированные колбэки вызываются в обратном порядке регистрации, в итоге это ведёт себя так, как если бы с зарегистрированным набором колбэков использовались несколько вложенных операторов [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with). Это распространяется даже на обработку исключений: если внутренний колбэк подавляет или заменяет исключение, то внешние колбэки получат аргументы на основе этого обновлённого состояния.245246Это относительно низкоуровневый API, который заботится о деталях корректной размотки стека колбэков выхода. Он предоставляет подходящую основу для менеджеров контекста более высокого уровня, которые манипулируют стеком выхода специфичным для приложения образом.247248Новое в версии 3.3.249250#### `enter_context(cm)`251252Входит в новый менеджер контекста и добавляет его метод [`__exit__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__exit__) в стек колбэков. Возвращаемое значение – результат собственного метода менеджера контекста [`__enter__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__enter__).253254Эти менеджеры контекста могут подавлять исключения так же, как если бы они использовались непосредственно как часть оператора [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with).255256#### `push(exit)`257258Добавляет метод [`__exit__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__exit__) менеджера контекста в стек колбэков.259260Поскольку `__enter__` *не* вызывается, этот метод можно использовать, чтобы покрыть часть реализации [`__enter__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__enter__) собственным методом [`__exit__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__exit__) менеджера контекста.261262Если передан объект, не являющийся менеджером контекста, этот метод считает, что это колбэк с той же сигнатурой, что и метод [`__exit__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__exit__) менеджера контекста, и добавляет его напрямую в стек колбэков.263264Возвращая истинные значения, эти колбэки могут подавлять исключения так же, как это делают методы [`__exit__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__exit__) менеджера контекста.265266Переданный объект возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.267268#### `callback(callback, *args, **kwds)`269270Принимает произвольную функцию-колбэк и аргументы и добавляет её в стек колбэков.271272В отличие от других методов, колбэки, добавленные таким образом, не могут подавлять исключения (поскольку им никогда не передаются сведения об исключении).273274Переданный колбэк возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.275276#### `pop_all()`277278Передаёт стек колбэков новому экземпляру [`ExitStack`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ExitStack) и возвращает его. Ни один колбэк при этой операции не вызывается – вместо этого они будут вызваны при закрытии нового стека (явно или неявно в конце оператора [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with)).279280Например, группу файлов можно открыть как операцию «всё или ничего» следующим образом:281282```python283with ExitStack() as stack:284 files = [stack.enter_context(open(fname)) for fname in filenames]285 # Сохраните метод close, но пока не вызывайте его.286 close_files = stack.pop_all().close287 # Если открытие любого файла завершается неудачей, все ранее открытые файлы будут288 # закрыты автоматически. Если все файлы открыты успешно,289 # они останутся открытыми даже после завершения оператора with.290 # close_files() затем можно вызвать явно, чтобы закрыть их все.291```292293#### `close()`294295Немедленно разворачивает стек колбэков, вызывая колбэки в порядке, обратном регистрации. Для любых зарегистрированных менеджеров контекста и колбэков выхода переданные аргументы будут указывать, что исключения не произошло.296297## 29.6.2. Примеры и рецепты298299В этом разделе описаны некоторые примеры и рецепты для эффективного использования инструментов, предоставляемых модулем [`contextlib`](https://python-all.ru/3.4/library/contextlib.html#module-contextlib).300301### 29.6.2.1. Поддержка переменного числа менеджеров контекста302303Основной вариант использования [`ExitStack`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ExitStack) описан в документации класса: поддержка переменного числа менеджеров контекста и других операций очистки в одном операторе [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with). Вариативность может быть обусловлена тем, что количество необходимых менеджеров контекста определяется пользовательским вводом (например, открытие указанной пользователем коллекции файлов), или тем, что некоторые менеджеры контекста являются необязательными:304305```python306with ExitStack() as stack:307 for resource in resources:308 stack.enter_context(resource)309 if need_special_resource():310 special = acquire_special_resource()311 stack.callback(release_special_resource, special)312 # Выполните операции, использующие полученные ресурсы313```314315Как показано, [`ExitStack`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ExitStack) также позволяет довольно легко использовать операторы [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with) для управления произвольными ресурсами, которые изначально не поддерживают протокол управления контекстом.316317### 29.6.2.2. Упрощение поддержки одного опционального менеджера контекста318319В частном случае одного необязательного менеджера контекста экземпляры [`ExitStack`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ExitStack) можно использовать как «ничего не делающий» менеджер контекста, что позволяет легко опустить менеджер контекста, не влияя на общую структуру исходного кода:320321```python322def debug_trace(details):323 if __debug__:324 return TraceContext(details)325 # В релизном режиме никаких особых действий с контекстом не выполняется326 return ExitStack()327328with debug_trace():329 # В режиме отладки набор трассируется, в остальных случаях работает обычным образом330```331332### 29.6.2.3. Перехват исключений из методов `__enter__`333334Иногда бывает желательно перехватывать исключения из реализации метода `__enter__`, *не* перехватывая случайно исключения из тела оператора [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with) или метода `__exit__` менеджера контекста. Используя [`ExitStack`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ExitStack), можно немного разделить шаги протокола управления контекстом, чтобы добиться этого:335336```python337stack = ExitStack()338try:339 x = stack.enter_context(cm)340except Exception:341 # обработать исключение __enter__342else:343 with stack:344 # Обработка нормального случая345```346347Необходимость в этом, вероятно, указывает на то, что базовый API должен предоставлять прямой интерфейс управления ресурсами для использования с операторами [`try`](https://python-all.ru/3.4/reference/compound_stmts.html#try)/[`except`](https://python-all.ru/3.4/reference/compound_stmts.html#except)/[`finally`](https://python-all.ru/3.4/reference/compound_stmts.html#finally), но не все API хорошо спроектированы в этом отношении. Когда менеджер контекста является единственным предоставляемым API управления ресурсами, [`ExitStack`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ExitStack) может упростить обработку различных ситуаций, которые невозможно обработать напрямую в операторе [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with).348349### 29.6.2.4. Очистка в реализации `__enter__`350351Как отмечено в документации [`ExitStack.push()`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ExitStack.push), этот метод может быть полезен для очистки уже выделенного ресурса, если последующие шаги в реализации [`__enter__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__enter__) завершатся неудачей.352353Вот пример реализации этого для менеджера контекста, который принимает функции захвата и освобождения ресурса, а также опциональную функцию проверки, и отображает их на протокол управления контекстом:354355```python356from contextlib import contextmanager, ExitStack357358class ResourceManager:359360 def __init__(self, acquire_resource, release_resource, check_resource_ok=None):361 self.acquire_resource = acquire_resource362 self.release_resource = release_resource363 if check_resource_ok is None:364 def check_resource_ok(resource):365 return True366 self.check_resource_ok = check_resource_ok367368 @contextmanager369 def _cleanup_on_error(self):370 with ExitStack() as stack:371 stack.push(self)372 yield373 # Проверка прошла успешно и не вызвала исключение374 # Соответственно, мы хотим сохранить ресурс и передать его375 # обратно вызывающему коду376 stack.pop_all()377378 def __enter__(self):379 resource = self.acquire_resource()380 with self._cleanup_on_error():381 if not self.check_resource_ok(resource):382 msg = "Failed validation for {!r}"383 raise RuntimeError(msg.format(resource))384 return resource385386 def __exit__(self, *exc_details):387 # Нам не нужно дублировать логику освобождения ресурсов388 self.release_resource()389```390391### 29.6.2.5. Замена любого использования `try-finally` и флаговых переменных392393Иногда можно встретить шаблон с оператором `try-finally` и флаговой переменной, указывающей, должно ли выполняться тело пункта `finally`. В своей простейшей форме (которую нельзя обработать простым использованием пункта `except`) это выглядит примерно так:394395```python396cleanup_needed = True397try:398 result = perform_operation()399 if result:400 cleanup_needed = False401finally:402 if cleanup_needed:403 cleanup_resources()404```405406Как и любой код на основе оператора `try`, это может создавать проблемы при разработке и проверке, поскольку код настройки и код очистки могут оказаться разделёнными произвольно длинными участками кода.407408[`ExitStack`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ExitStack) позволяет вместо этого зарегистрировать колбэк для выполнения в конце оператора `with`, а затем позже решить пропустить выполнение этого колбэка:409410```python411from contextlib import ExitStack412413with ExitStack() as stack:414 stack.callback(cleanup_resources)415 result = perform_operation()416 if result:417 stack.pop_all()418```419420Это позволяет заранее явно указать предполагаемое поведение очистки, вместо того чтобы требовать отдельную переменную-флаг.421422Если конкретное приложение часто использует этот шаблон, его можно упростить еще больше с помощью небольшого вспомогательного класса:423424```python425from contextlib import ExitStack426427class Callback(ExitStack):428 def __init__(self, callback, *args, **kwds):429 super(Callback, self).__init__()430 self.callback(callback, *args, **kwds)431432 def cancel(self):433 self.pop_all()434435with Callback(cleanup_resources) as cb:436 result = perform_operation()437 if result:438 cb.cancel()439```440441Если очистка ресурса ещё не аккуратно оформлена в виде отдельной функции, всё равно можно использовать форму декоратора [`ExitStack.callback()`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ExitStack.callback), чтобы объявить очистку ресурса заранее:442443```python444from contextlib import ExitStack445446with ExitStack() as stack:447 @stack.callback448 def cleanup_resources():449 ...450 result = perform_operation()451 if result:452 stack.pop_all()453```454455Из-за того, как работает протокол декоратора, функция обратного вызова, объявленная таким образом, не может принимать никаких параметров. Вместо этого любые ресурсы, которые необходимо освободить, должны быть доступны как переменные замыкания.456457### 29.6.2.6. Использование менеджера контекста в качестве декоратора функции458459[`ContextDecorator`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ContextDecorator) позволяет использовать менеджер контекста как в обычном операторе `with`, так и в качестве декоратора функции.460461Например, иногда бывает полезно обернуть функции или группы операторов в логгер, который может отслеживать время входа и время выхода. Вместо того чтобы писать и декоратор функции, и менеджер контекста для этой задачи, наследование от [`ContextDecorator`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ContextDecorator) предоставляет обе возможности в одном определении:462463```python464from contextlib import ContextDecorator465import logging466467logging.basicConfig(level=logging.INFO)468469class track_entry_and_exit(ContextDecorator):470 def __init__(self, name):471 self.name = name472473 def __enter__(self):474 logging.info('Entering: {}'.format(self.name))475476 def __exit__(self, exc_type, exc, exc_tb):477 logging.info('Exiting: {}'.format(self.name))478```479480Экземпляры этого класса можно использовать как менеджер контекста:481482```python483with track_entry_and_exit('widget loader'):484 print('Some time consuming activity goes here')485 load_widget()486```487488А также как декоратор функции:489490```python491@track_entry_and_exit('widget loader')492def activity():493 print('Some time consuming activity goes here')494 load_widget()495```496497Обратите внимание, что есть ещё одно ограничение при использовании менеджеров контекста в качестве декораторов функций: нет способа получить доступ к возвращаемому значению [`__enter__()`](https://python-all.ru/3.4/reference/datamodel.html#object.__enter__). Если это значение необходимо, всё равно нужно использовать явный оператор `with`.498499> **См. также**500>501> **[**PEP 0343**](https://python-all.ru/3.4/library/contextlib.html) – оператор «with»**502>503> Спецификация, предыстория и примеры для оператора504>505> [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with)506>507> в Python.508509## 29.6.3. Одноразовые, повторно используемые и реентерабельные менеджеры контекста510511Большинство менеджеров контекста написаны так, что их можно эффективно использовать в операторе [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with) только один раз. Эти одноразовые менеджеры контекста должны создаваться заново каждый раз при использовании – попытка использовать их второй раз вызовет исключение или не будет работать правильно.512513Это распространённое ограничение означает, что обычно рекомендуется создавать менеджеры контекста непосредственно в заголовке оператора [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with), где они используются (как показано во всех примерах выше).514515Файлы являются примером фактически одноразовых менеджеров контекста, поскольку первый оператор [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with) закроет файл, предотвращая любые дальнейшие операции ввода-вывода с использованием этого файлового объекта.516517Контекстные менеджеры, созданные с помощью [`contextmanager()`](https://python-all.ru/3.4/library/contextlib.html#contextlib.contextmanager), также являются одноразовыми контекстными менеджерами. Если попытаться использовать их второй раз, они сообщат, что внутренний генератор не выполняет yield:518519```python520>>> from contextlib import contextmanager521>>> @contextmanager522... def singleuse():523... print("Before")524... yield525... print("After")526...527>>> cm = singleuse()528>>> with cm:529... pass530...531Before532After533>>> with cm:534... pass535...536Traceback (most recent call last):537 ...538RuntimeError: generator didn't yield539```540541### 29.6.3.1. Реентерабельные менеджеры контекста542543Более сложные контекстные менеджеры могут быть «реентерабельными». Такие контекстные менеджеры можно не только использовать в нескольких инструкциях [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with), но и *внутри* инструкции [`with`](https://python-all.ru/3.4/reference/compound_stmts.html#with), которая уже использует тот же контекстный менеджер.544545[`threading.RLock`](https://python-all.ru/3.4/library/threading.html#threading.RLock) является примером реентерабельного контекстного менеджера, как и [`suppress()`](https://python-all.ru/3.4/library/contextlib.html#contextlib.suppress) и [`redirect_stdout()`](https://python-all.ru/3.4/library/contextlib.html#contextlib.redirect_stdout). Вот очень простой пример реентерабельного использования:546547```python548>>> from contextlib import redirect_stdout549>>> from io import StringIO550>>> stream = StringIO()551>>> write_to_stream = redirect_stdout(stream)552>>> with write_to_stream:553... print("This is written to the stream rather than stdout")554... with write_to_stream:555... print("This is also written to the stream")556...557>>> print("This is written directly to stdout")558This is written directly to stdout559>>> print(stream.getvalue())560This is written to the stream rather than stdout561This is also written to the stream562```563564Реальные примеры реентерабельности, скорее всего, будут включать несколько функций, вызывающих друг друга, и, следовательно, будут гораздо сложнее этого примера.565566Также обратите внимание, что реентерабельность – это *не* то же самое, что потокобезопасность. Например, [`redirect_stdout()`](https://python-all.ru/3.4/library/contextlib.html#contextlib.redirect_stdout) определённо не является потокобезопасным, поскольку он вносит глобальное изменение в состояние системы, привязывая [`sys.stdout`](https://python-all.ru/3.4/library/sys.html#sys.stdout) к другому потоку данных.567568### 29.6.3.2. Повторно используемые менеджеры контекста569570Отличающиеся от одноразовых и реентерабельных менеджеров контекста – «многократно используемые» менеджеры контекста (или, если быть совсем точным, «многократно используемые, но не реентерабельные» менеджеры контекста, поскольку реентерабельные менеджеры контекста также являются многократно используемыми). Эти менеджеры контекста поддерживают многократное использование, но завершатся ошибкой (или иным образом будут работать некорректно), если конкретный экземпляр менеджера контекста уже был использован во внешней инструкции with.571572[`threading.Lock`](https://python-all.ru/3.4/library/threading.html#threading.Lock) является примером повторно используемого, но не реентерабельного контекстного менеджера (для реентерабельной блокировки необходимо использовать [`threading.RLock`](https://python-all.ru/3.4/library/threading.html#threading.RLock)).573574Ещё одним примером повторно используемого, но не реентерабельного контекстного менеджера является [`ExitStack`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ExitStack), поскольку он вызывает *все* зарегистрированные на данный момент колбэки при выходе из любой инструкции with, независимо от того, где эти колбэки были добавлены:575576```python577>>> from contextlib import ExitStack578>>> stack = ExitStack()579>>> with stack:580... stack.callback(print, "Callback: from first context")581... print("Leaving first context")582...583Leaving first context584Callback: from first context585>>> with stack:586... stack.callback(print, "Callback: from second context")587... print("Leaving second context")588...589Leaving second context590Callback: from second context591>>> with stack:592... stack.callback(print, "Callback: from outer context")593... with stack:594... stack.callback(print, "Callback: from inner context")595... print("Leaving inner context")596... print("Leaving outer context")597...598Leaving inner context599Callback: from inner context600Callback: from outer context601Leaving outer context602```603604Как показывает вывод примера, повторное использование одного объекта стека в нескольких инструкциях with работает корректно, но попытка вложить их приведет к очистке стека в конце самой внутренней инструкции with, что вряд ли является желательным поведением.605606Использование отдельных экземпляров [`ExitStack`](https://python-all.ru/3.4/library/contextlib.html#contextlib.ExitStack) вместо повторного использования одного экземпляра позволяет избежать этой проблемы:607608```python609>>> from contextlib import ExitStack610>>> with ExitStack() as outer_stack:611... outer_stack.callback(print, "Callback: from outer context")612... with ExitStack() as inner_stack:613... inner_stack.callback(print, "Callback: from inner context")614... print("Leaving inner context")615... print("Leaving outer context")616...617Leaving inner context618Callback: from inner context619Leaving outer context620Callback: from outer context621```622