contextlib — Утилиты для контекстов оператора with
Исходный код: Lib/contextlib.py
Этот модуль предоставляет утилиты для общих задач, связанных с оператором with. Для получения дополнительной информации см. также Типы менеджеров контекста и Менеджеры контекста оператора With.
Утилиты
Предоставленные функции и классы:
-
class contextlib.AbstractContextManager -
Абстрактный базовый класс для классов, реализующих
object.__enter__()иobject.__exit__(). Предоставлена реализация по умолчанию дляobject.__enter__(), которая возвращаетself, в то время какobject.__exit__()— это абстрактный метод, который по умолчанию возвращаетNone. См. также определение Типов менеджеров контекста.Введено в версии 3.6.
-
class contextlib.AbstractAsyncContextManager -
Абстрактный базовый класс для классов, реализующих
object.__aenter__()иobject.__aexit__(). Предоставлена реализация по умолчанию дляobject.__aenter__(), которая возвращаетself, в то время какobject.__aexit__()— это абстрактный метод, который по умолчанию возвращаетNone. См. также определение Асинхронных менеджеров контекста.Введено в версии 3.7.
-
@contextlib.contextmanager -
Эта функция является декоратором, который может использоваться для определения функции-фабрики для менеджеров контекста оператора
withбез необходимости создания класса или отдельных__enter__()и__exit__()методов.Хотя многие объекты изначально поддерживают использование в операторах with, иногда необходимо управлять ресурсом, который сам по себе не является менеджером контекста и не реализует метод
close()для использования сcontextlib.closing.Абстрактный пример, как обеспечить правильное управление ресурсами:
from contextlib import contextmanager @contextmanager def managed_resource(*args, **kwds): # Code to acquire resource, e.g.: resource = acquire_resource(*args, **kwds) try: yield resource finally: # Code to release resource, e.g.: release_resource(resource)Функцию можно использовать следующим образом:
>>> with managed_resource(timeout=3600) as resource: ... # Resource is released at the end of this block, ... # even if code in the block raises an exception
Декорируемая функция должна возвращать генератор-итератор при вызове. Этот итератор должен возвращать ровно одно значение, которое будет привязано к целевым переменным в
asоператораwith, если таковые есть.В момент возврата значения генератором выполняется блок, вложенный в оператор
with. Генератор возобновляется после выхода из блока. Если в блоке возникает необработанное исключение, оно повторно возникает внутри генератора в точке, где произошло yield. Таким образом, можно использовать операторtry…except…finallyдля перехвата ошибки (если она есть) или для обеспечения выполнения некоторой очистки. Если исключение перехватывается только для того, чтобы его записать в журнал или выполнить какое-либо действие (а не для того, чтобы полностью его подавить), генератор должен повторно вызвать это исключение. В противном случае менеджер контекста генератора укажет операторуwithчто исключение обработано, и выполнение продолжится со следующей строкой после оператораwith.contextmanager()используетContextDecorator, поэтому создаваемые менеджеры контекста могут использоваться как декораторы, а также в операторахwith. При использовании в качестве декоратора новый экземпляр генератора неявно создается при каждом вызове функции (это позволяет менеджерам контекста, созданнымcontextmanager(), удовлетворить требованию о поддержке множественных вызовов, чтобы их можно было использовать в качестве декораторов).Изменено в версии 3.2: Использование
ContextDecorator.
-
@contextlib.asynccontextmanager -
Аналогично
contextmanager(), но создаёт асинхронный менеджер контекста.Эта функция является декоратором, который может использоваться для определения функции-фабрики для асинхронных менеджеров контекста оператора
async withбез необходимости создания класса или отдельных__aenter__()и__aexit__()методов. Она должна применяться к функции-асинхронному генератору.Простой пример:
from contextlib import asynccontextmanager @asynccontextmanager async def get_connection(): conn = await acquire_db_connection() try: yield conn finally: await release_db_connection(conn) async def get_all_users(): async with get_connection() as conn: return conn.query('SELECT ...')Введено в версии 3.7.
Менеджеры контекста, определённые с помощью
asynccontextmanager(), могут использоваться как декораторы или с операторамиasync with:import time from contextlib import asynccontextmanager @asynccontextmanager async def timeit(): now = time.monotonic() try: yield finally: print(f'it took {time.monotonic() - now}s to run') @timeit() async def main(): # ... async code ...При использовании в качестве декоратора новый экземпляр генератора неявно создаётся при каждом вызове функции. Это позволяет асинхронным менеджерам контекста, созданным с помощью
asynccontextmanager(), удовлетворить требованию о поддержке множественных вызовов для использования в качестве декораторов.Изменено в версии 3.10: Асинхронные менеджеры контекста, созданные с помощью
asynccontextmanager(), могут использоваться в качестве декораторов.
-
contextlib.closing(thing) -
Возвращает менеджер контекста, который закрывает thing по завершении блока. Это в основном эквивалентно:
from contextlib import contextmanager @contextmanager def closing(thing): try: yield thing finally: thing.close()И позволяет вам писать код так:
from contextlib import closing from urllib.request import urlopen with closing(urlopen('https://www.python.org')) as page: for line in page: print(line)без необходимости явного закрытия
page. Даже если произойдёт ошибка,page.close()будет вызван при выходе из блокаwith.
-
contextlib.aclosing(thing) -
Возвращает асинхронный менеджер контекста, который вызывает метод
aclose()объекта thing по завершении блока. Это в основном эквивалентно:from contextlib import asynccontextmanager @asynccontextmanager async def aclosing(thing): try: yield thing finally: await thing.aclose()Важно, что
aclosing()поддерживает детерминированную очистку асинхронных генераторов, когда они завершаются преждевременно с помощьюbreakили исключения. Например:from contextlib import aclosing async with aclosing(my_generator()) as values: async for value in values: if value == 42: breakЭтот шаблон гарантирует, что код асинхронного выхода генератора выполняется в том же контексте, что и его итерации (так что исключения и переменные контекста работают как ожидается, а код выхода не выполняется после окончания срока действия какой-либо задачи, от которой он зависит).
Введено в версии 3.10.
-
contextlib.nullcontext(enter_result=None) -
Возвращает менеджер контекста, возвращающий enter_result из
__enter__, но в остальном ничего не делает. Он предназначен для использования в качестве заглушки для необязательного менеджера контекста, например:def myfunction(arg, ignore_exceptions=False): if ignore_exceptions: # Use suppress to ignore all exceptions. cm = contextlib.suppress(Exception) else: # Do not ignore any exceptions, cm has no effect. cm = contextlib.nullcontext() with cm: # Do somethingПример использования enter_result:
def process_file(file_or_path): if isinstance(file_or_path, str): # If string, open file cm = open(file_or_path) else: # Caller is responsible for closing file cm = nullcontext(file_or_path) with cm as file: # Perform processing on the fileТакже может использоваться как заглушка для асинхронных менеджеров контекста:
async def send_http(session=None): if not session: # If no http session, create it with aiohttp cm = aiohttp.ClientSession() else: # Caller is responsible for closing the session cm = nullcontext(session) async with cm as session: # Send http requests with sessionВведено в версии 3.7.
Изменено в версии 3.10: Добавлена поддержка асинхронных менеджеров контекста.
-
contextlib.suppress(*exceptions) -
Возвращает менеджер контекста, который подавляет любые из указанных исключений, если они возникнут в теле оператора
with, а затем возобновляет выполнение с первой инструкции, следующей за концом оператораwith.Как и любой другой механизм, полностью подавляющий исключения, этот менеджер контекста следует использовать только для очень специфических ошибок, где известно, что беспрепятственное продолжение выполнения программы является правильным действием.
Например:
from contextlib import suppress with suppress(FileNotFoundError): os.remove('somefile.tmp') with suppress(FileNotFoundError): os.remove('someotherfile.tmp')Этот код эквивалентен:
try: os.remove('somefile.tmp') except FileNotFoundError: pass try: os.remove('someotherfile.tmp') except FileNotFoundError: passЭтот менеджер контекста является реентерабельным.
Введено в версии 3.4.
-
contextlib.redirect_stdout(new_target) -
Менеджер контекста для временного перенаправления
sys.stdoutна другой файл или объект, подобный файлу.Этот инструмент добавляет гибкость в существующие функции или классы, вывод которых жёстко привязан к stdout.
Например, вывод
help()обычно отправляется в sys.stdout. Вы можете захватить этот вывод в строку, перенаправив вывод на объектio.StringIO. Заменяемый поток возвращается методом__enter__и, таким образом, доступен в качестве целевого объекта оператораwith:with redirect_stdout(io.StringIO()) as f: help(pow) s = f.getvalue()Чтобы направить вывод
help()в файл на диске, перенаправьте вывод в обычный файл:with open('help.txt', 'w') as f: with redirect_stdout(f): help(pow)Чтобы направить вывод
help()в sys.stderr:with redirect_stdout(sys.stderr): help(pow)Обратите внимание, что глобальное побочное действие на
sys.stdoutозначает, что этот менеджер контекста не подходит для использования в библиотечном коде и большинстве потоковых приложений. Он также не влияет на вывод подпроцессов. Однако это всё ещё полезный подход для многих утилитарных скриптов.Этот менеджер контекста является реентерабельным.
Введено в версии 3.4.
-
contextlib.redirect_stderr(new_target) -
Аналогично
redirect_stdout(), но перенаправляетsys.stderrна другой файл или объект, подобный файлу.Этот менеджер контекста является реентерабельным.
Введено в версии 3.5.
-
class contextlib.ContextDecorator -
Базовый класс, который позволяет менеджеру контекста также использоваться в качестве декоратора.
Менеджеры контекста, наследующие от
ContextDecorator, должны реализовывать__enter__и__exit__как обычно.__exit__сохраняет свою необязательную обработку исключений даже при использовании в качестве декоратора.ContextDecoratorиспользуетсяcontextmanager(), поэтому вы получаете эту функциональность автоматически.Пример
ContextDecorator:from contextlib import ContextDecorator class mycontext(ContextDecorator): def __enter__(self): print('Starting') return self def __exit__(self, *exc): print('Finishing') return FalseЗатем класс можно использовать следующим образом:
>>> @mycontext() ... def function(): ... print('The bit in the middle') ... >>> function() Starting The bit in the middle Finishing >>> with mycontext(): ... print('The bit in the middle') ... Starting The bit in the middle FinishingЭто изменение — всего лишь синтаксический сахар для любой конструкции следующего вида:
def f(): with cm(): # Do stuffContextDecoratorпозволяет вместо этого написать:@cm() def f(): # Do stuffЭто делает ясным, что
cmприменяется ко всей функции, а не только к её части (и экономия уровня отступа тоже неплоха).Существующие менеджеры контекста, которые уже имеют базовый класс, могут быть расширены, используя
ContextDecoratorкак класс-микстин:from contextlib import ContextDecorator class mycontext(ContextBaseClass, ContextDecorator): def __enter__(self): return self def __exit__(self, *exc): return FalseПримечание
Поскольку декорируемая функция должна быть вызываема многократно, базовый менеджер контекста должен поддерживать использование во множестве операторов
with. Если это не так, следует использовать исходную конструкцию с явным операторомwithвнутри функции.Введено в версии 3.2.
-
class contextlib.AsyncContextDecorator -
Аналогично
ContextDecorator, но только для асинхронных функций.Пример
AsyncContextDecorator:from asyncio import run from contextlib import AsyncContextDecorator class mycontext(AsyncContextDecorator): async def __aenter__(self): print('Starting') return self async def __aexit__(self, *exc): print('Finishing') return FalseЗатем класс можно использовать следующим образом:
>>> @mycontext() ... async def function(): ... print('The bit in the middle') ... >>> run(function()) Starting The bit in the middle Finishing >>> async def function(): ... async with mycontext(): ... print('The bit in the middle') ... >>> run(function()) Starting The bit in the middle FinishingВведено в версии 3.10.
-
class contextlib.ExitStack -
Менеджер контекста, предназначенный для удобного программирования комбинирования других менеджеров контекста и функций очистки, особенно тех, которые являются необязательными или каким-либо другим образом управляются входными данными.
Например, набор файлов легко обрабатывается в одном операторе with следующим образом:
with ExitStack() as stack: files = [stack.enter_context(open(fname)) for fname in filenames] # All opened files will automatically be closed at the end of # the with statement, even if attempts to open files later # in the list raise an exceptionМетод
__enter__()возвращает экземплярExitStackи не выполняет дополнительных операций.Каждый экземпляр поддерживает стек зарегистрированных обратных вызовов, которые вызываются в обратном порядке при закрытии экземпляра (явно или неявно в конце оператора
with). Обратите внимание, что обратные вызовы не вызываются неявно при удалении экземпляра стека контекста из памяти.Эта модель стека используется, чтобы менеджеры контекста, которые получают свои ресурсы в методе
__init__(например, объекты файлов), могли обрабатываться правильно.Поскольку зарегистрированные обратные вызовы вызываются в обратном порядке регистрации, это в итоге ведет себя так, как будто использовались несколько вложенных операторов
withсо множеством зарегистрированных обратных вызовов. Это распространяется даже на обработку исключений — если внутренний обратный вызов подавляет или заменяет исключение, то внешние обратные вызовы будут получать аргументы, основанные на этом обновленном состоянии.Это относительно низкоуровневый API, который заботится о деталях правильного разматывания стека обратных вызовов выхода. Он предоставляет подходящую основу для менеджеров контекста более высокого уровня, которые манипулируют стеком выхода в прикладных способах.
Введено в версии 3.3.
-
enter_context(cm) -
Входит в новый менеджер контекста и добавляет его метод
__exit__()в стек обратных вызовов. Возвращаемое значение — результат собственного метода менеджера контекста__enter__().Эти менеджеры контекста могут подавлять исключения так же, как и при непосредственном использовании в операторе
with.
-
push(exit) -
Добавляет метод
__exit__()менеджера контекста в стек обратных вызовов.Так как
__enter__не вызывается, этот метод можно использовать для охвата части реализации__enter__()менеджером контекста с собственным методом__exit__().Если передано объект, который не является менеджером контекста, этот метод предполагает, что это обратный вызов с той же сигнатурой, что и метод менеджера контекста
__exit__(), и добавляет его непосредственно в стек обратных вызовов.Возвращая значения true, эти обратные вызовы могут подавлять исключения так же, как и методы менеджеров контекста
__exit__().Переданный объект возвращается из функции, позволяя использовать этот метод в качестве декоратора функции.
-
callback(callback, /, *args, **kwds) -
Принимает произвольную функцию обратного вызова и аргументы и добавляет её в стек обратных вызовов.
В отличие от других методов, обратные вызовы, добавленные таким образом, не могут подавлять исключения (поскольку им никогда не передаются подробности об исключении).
Переданный обратный вызов возвращается из функции, позволяя использовать этот метод в качестве декоратора функции.
-
pop_all() -
Переносит стек обратных вызовов в новый экземпляр
ExitStackи возвращает его. Ни один обратный вызов не вызывается этой операцией — вместо этого они будут вызваны при закрытии нового стека (явно или неявно в конце оператораwith).Например, группа файлов может быть открыта как операция «всё или ничего» следующим образом:
with ExitStack() as stack: files = [stack.enter_context(open(fname)) for fname in filenames] # Hold onto the close method, but don't call it yet. close_files = stack.pop_all().close # If opening any file fails, all previously opened files will be # closed automatically. If all files are opened successfully, # they will remain open even after the with statement ends. # close_files() can then be invoked explicitly to close them all.
-
close() -
Немедленно разматывает стек обратных вызовов, вызывая обратные вызовы в обратном порядке регистрации. Для всех менеджеров контекста и зарегистрированных обратных вызовов выхода, передаваемые аргументы будут указывать, что исключение не возникло.
-
-
class contextlib.AsyncExitStack -
Асинхронный менеджер контекста, аналогичный асинхронному менеджеру контекста, который поддерживает объединение синхронных и асинхронных менеджеров контекста, а также корутины для логики очистки.
Метод
close()не реализован, вместо него следует использоватьaclose().-
coroutine enter_async_context(cm) -
Аналогично
enter_context(), но ожидает асинхронный менеджер контекста.
-
push_async_exit(exit) -
Аналогично
push(), но ожидает либо асинхронный менеджер контекста, либо функцию-корутину.
-
push_async_callback(callback, /, *args, **kwds) -
Аналогично
callback(), но ожидает функцию-корутину.
-
coroutine aclose() -
Аналогично
close(), но корректно обрабатывает awaitables.
Продолжение примера для
asynccontextmanager():async with AsyncExitStack() as stack: connections = [await stack.enter_async_context(get_connection()) for i in range(5)] # All opened connections will automatically be released at the end of # the async with statement, even if attempts to open a connection # later in the list raise an exception.Введено в версии 3.7.
-
Примеры и рецепты
В этом разделе описаны примеры и рецепты эффективного использования инструментов, предоставляемых модулем contextlib.
Поддержка переменного количества менеджеров контекста
Основной случай использования ExitStack — поддержка переменного количества менеджеров контекста и других операций очистки в одном операторе with. Изменчивость может быть вызвана количеством менеджеров контекста, необходимых в зависимости от входных данных пользователя (например, открытие указанного пользователем набора файлов), или наличием необязательных менеджеров контекста:
with ExitStack() as stack:
for resource in resources:
stack.enter_context(resource)
if need_special_resource():
special = acquire_special_resource()
stack.callback(release_special_resource, special)
# Perform operations that use the acquired resources
Как показано, ExitStack также позволяет легко использовать операторы with для управления произвольными ресурсами, которые не поддерживают протокол управления контекстом по умолчанию.
Перехват исключений из методов __enter__
Иногда желательно перехватывать исключения из реализации метода __enter__ без непреднамеренного перехвата исключений из тела оператора with или метода __exit__ менеджера контекста. Используя ExitStack, шаги протокола управления контекстом можно немного разделить, чтобы позволить это:
stack = ExitStack()
try:
x = stack.enter_context(cm)
except Exception:
# handle __enter__ exception
else:
with stack:
# Handle normal case
На самом деле такая необходимость, скорее всего, указывает на то, что базовому API следует предоставлять интерфейс управления ресурсами для использования с операторами try/except/finally, но не все API хорошо спроектированы в этом отношении. Когда менеджер контекста является единственным API управления ресурсами, ExitStack может упростить обработку различных ситуаций, которые не могут быть обработаны непосредственно в операторе with.
Очистка в реализации метода __enter__
Как отмечено в документации ExitStack.push(), этот метод может быть полезен для очистки уже выделенного ресурса, если последующие шаги в реализации метода __enter__() завершатся неудачно.
Вот пример такого использования для менеджера контекста, который принимает функции приобретения и освобождения ресурсов, а также необязательную функцию проверки и сопоставляет их с протоколом управления контекстом:
from contextlib import contextmanager, AbstractContextManager, ExitStack
class ResourceManager(AbstractContextManager):
def __init__(self, acquire_resource, release_resource, check_resource_ok=None):
self.acquire_resource = acquire_resource
self.release_resource = release_resource
if check_resource_ok is None:
def check_resource_ok(resource):
return True
self.check_resource_ok = check_resource_ok
@contextmanager
def _cleanup_on_error(self):
with ExitStack() as stack:
stack.push(self)
yield
# The validation check passed and didn't raise an exception
# Accordingly, we want to keep the resource, and pass it
# back to our caller
stack.pop_all()
def __enter__(self):
resource = self.acquire_resource()
with self._cleanup_on_error():
if not self.check_resource_ok(resource):
msg = "Failed validation for {!r}"
raise RuntimeError(msg.format(resource))
return resource
def __exit__(self, *exc_details):
# We don't need to duplicate any of our resource release logic
self.release_resource()
Замена любого использования try-finally и флаговых переменных
Иногда встречается шаблон оператора try-finally с флаговой переменной, указывающей, следует ли выполнять тело оператора finally. В простейшей форме (которую нельзя уже обработать только с помощью оператора except вместо него) он выглядит примерно так:
cleanup_needed = True
try:
result = perform_operation()
if result:
cleanup_needed = False
finally:
if cleanup_needed:
cleanup_resources()
Как и в любом коде, основанном на операторе try, это может вызвать проблемы при разработке и проверке, так как код настройки и код очистки могут быть разделены произвольно большими участками кода.
ExitStack позволяет вместо этого зарегистрировать обратный вызов для выполнения в конце оператора with, а затем позже решить пропустить выполнение этого обратного вызова:
from contextlib import ExitStack
with ExitStack() as stack:
stack.callback(cleanup_resources)
result = perform_operation()
if result:
stack.pop_all()
Это позволяет сделать предполагаемое поведение очистки явным с самого начала, а не требует отдельной флаговой переменной.
Если приложение часто использует такой шаблон, его можно ещё больше упростить с помощью небольшого вспомогательного класса:
from contextlib import ExitStack
class Callback(ExitStack):
def __init__(self, callback, /, *args, **kwds):
super().__init__()
self.callback(callback, *args, **kwds)
def cancel(self):
self.pop_all()
with Callback(cleanup_resources) as cb:
result = perform_operation()
if result:
cb.cancel()
Если очистка ресурса не упакована в отдельную функцию, можно использовать декораторский вариант ExitStack.callback() для объявления очистки ресурса заранее:
from contextlib import ExitStack
with ExitStack() as stack:
@stack.callback
def cleanup_resources():
...
result = perform_operation()
if result:
stack.pop_all()
Из-за того, как работает протокол декоратора, обратный вызов, объявленный таким образом, не может принимать никаких параметров. Вместо этого любые ресурсы, которые необходимо освободить, должны быть доступны как переменные замкнутого пространства.
Использование менеджера контекста в качестве декоратора функции
ContextDecorator позволяет использовать менеджер контекста как в обычном операторе with, так и в качестве декоратора функции.
Например, иногда бывает полезно обернуть функции или группы операторов логгером, который может отслеживать время входа и выхода. Вместо написания как декоратора функции, так и менеджера контекста для этой задачи, наследование от ContextDecorator предоставляет оба варианта в одном определении:
from contextlib import ContextDecorator
import logging
logging.basicConfig(level=logging.INFO)
class track_entry_and_exit(ContextDecorator):
def __init__(self, name):
self.name = name
def __enter__(self):
logging.info('Entering: %s', self.name)
def __exit__(self, exc_type, exc, exc_tb):
logging.info('Exiting: %s', self.name)
Экземпляры этого класса можно использовать как менеджер контекста:
with track_entry_and_exit('widget loader'):
print('Some time consuming activity goes here')
load_widget()
И также как декоратор функции:
@track_entry_and_exit('widget loader')
def activity():
print('Some time consuming activity goes here')
load_widget()
Обратите внимание, что при использовании менеджеров контекста в качестве декораторов функций существует одно дополнительное ограничение: нет способа получить возвращаемое значение __enter__(). Если это значение необходимо, то всё равно необходимо использовать явное объявление with.
Одноразовые, многоразовые и рекурсивные контекстные менеджеры
Большинство контекстных менеджеров написаны так, что они могут быть эффективно использованы только в операторе with один раз. Эти одноразовые контекстные менеджеры должны создаваться заново каждый раз, когда они используются. Попытка использовать их во второй раз вызовет исключение или иным образом не будет работать правильно.
Это общее ограничение означает, что обычно рекомендуется создавать контекстные менеджеры непосредственно в заголовке оператора with, где они используются (как показано во всех примерах использования выше).
Файлы являются примером эффективно одноразовых контекстных менеджеров, поскольку первый оператор with закроет файл, предотвратив любые дальнейшие операции ввода-вывода с использованием этого объекта файла.
Контекстные менеджеры, созданные с помощью contextmanager(), также являются одноразовыми контекстными менеджерами и будут жаловаться на сбой генератора, если вы попытаетесь использовать их во второй раз:
>>> from contextlib import contextmanager
>>> @contextmanager
... def singleuse():
... print("Before")
... yield
... print("After")
...
>>> cm = singleuse()
>>> with cm:
... pass
...
Before
After
>>> with cm:
... pass
...
Traceback (most recent call last):
...
RuntimeError: generator didn't yield
Рекурсивные контекстные менеджеры
Более сложные контекстные менеджеры могут быть «рекурсивными». Эти контекстные менеджеры могут использоваться не только в нескольких операторах with, но и могут использоваться *внутри* оператора with, который уже использует тот же контекстный менеджер.
threading.RLock является примером рекурсивного контекстного менеджера, также как suppress() и redirect_stdout(). Вот очень простой пример рекурсивного использования:
>>> from contextlib import redirect_stdout
>>> from io import StringIO
>>> stream = StringIO()
>>> write_to_stream = redirect_stdout(stream)
>>> with write_to_stream:
... print("This is written to the stream rather than stdout")
... with write_to_stream:
... print("This is also written to the stream")
...
>>> print("This is written directly to stdout")
This is written directly to stdout
>>> print(stream.getvalue())
This is written to the stream rather than stdout
This is also written to the stream
Примеры рекурсивного использования в реальном мире, скорее всего, будут включать несколько функций, вызывающих друг друга, и, следовательно, будут намного сложнее, чем этот пример.
Обратите также внимание, что рекурсивность — это *не* то же самое, что безопасность потоков. redirect_stdout(), например, определенно не является безопасным для потоков, поскольку он производит глобальную модификацию состояния системы, привязывая sys.stdout к другому потоку.
Многоразовые контекстные менеджеры
Отличающиеся как от одноразовых, так и от рекурсивных контекстных менеджеров — «многоразовые» контекстные менеджеры (или, чтобы быть полностью ясными, «многоразовые, но не рекурсивные» контекстные менеджеры, поскольку рекурсивные контекстные менеджеры также многоразовые). Эти контекстные менеджеры поддерживают многократное использование, но они завершатся ошибкой (или иным образом не будут работать правильно), если конкретный экземпляр контекстного менеджера уже использовался в содержащем операторе with.
threading.Lock является примером многоразового, но не рекурсивного контекстного менеджера (для рекурсивной блокировки необходимо использовать threading.RLock).
Еще одним примером многоразового, но не рекурсивного контекстного менеджера является ExitStack, так как он вызывает *все* в настоящее время зарегистрированные обратные вызовы при выходе из любого оператора with, независимо от того, где эти обратные вызовы были добавлены:
>>> from contextlib import ExitStack
>>> stack = ExitStack()
>>> with stack:
... stack.callback(print, "Callback: from first context")
... print("Leaving first context")
...
Leaving first context
Callback: from first context
>>> with stack:
... stack.callback(print, "Callback: from second context")
... print("Leaving second context")
...
Leaving second context
Callback: from second context
>>> with stack:
... stack.callback(print, "Callback: from outer context")
... with stack:
... stack.callback(print, "Callback: from inner context")
... print("Leaving inner context")
... print("Leaving outer context")
...
Leaving inner context
Callback: from inner context
Callback: from outer context
Leaving outer context
Как показывает вывод из примера, повторное использование одного объекта стека в нескольких операторах with работает правильно, но попытка их вложения приведет к очистке стека в конце самого внутреннего оператора with, что, скорее всего, не является желаемым поведением.
Использование отдельных экземпляров ExitStack вместо повторного использования одного экземпляра предотвращает эту проблему:
>>> from contextlib import ExitStack
>>> with ExitStack() as outer_stack:
... outer_stack.callback(print, "Callback: from outer context")
... with ExitStack() as inner_stack:
... inner_stack.callback(print, "Callback: from inner context")
... print("Leaving inner context")
... print("Leaving outer context")
...
Leaving inner context
Callback: from inner context
Leaving outer context
Callback: from outer context
© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/library/contextlib.html