contextlib
— Утилиты для with
- контекстов утверждений¶
Исходный код: Lib/contextlib.py.
Этот модуль предоставляет утилиты для решения общих задач, связанных с оператором with
. Для получения дополнительной информации смотрите также Типы контекстного менеджера и С менеджерами по контексту заявлений.
Утилиты¶
Предоставляемые функции и занятия:
-
class
contextlib.
AbstractContextManager
¶ abstract base class для классов, реализующих
object.__enter__()
иobject.__exit__()
. Дляobject.__enter__()
предусмотрена реализация по умолчанию, которая возвращаетself
, аobject.__exit__()
является абстрактным методом, который по умолчанию возвращаетNone
. См. также определение Типы контекстного менеджера.Добавлено в версии 3.6.
-
class
contextlib.
AbstractAsyncContextManager
¶ abstract base class для классов, реализующих
object.__aenter__()
иobject.__aexit__()
. Дляobject.__aenter__()
предусмотрена реализация по умолчанию, которая возвращаетself
, аobject.__aexit__()
является абстрактным методом, который по умолчанию возвращаетNone
. См. также определение Асинхронные контекстные менеджеры.Добавлено в версии 3.7.
-
@
contextlib.
contextmanager
¶ Эта функция является decorator, которая может быть использована для определения фабричной функции для менеджеров контекста оператора
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
Украшаемая функция при вызове должна возвращать generator-итератор. Этот итератор должен давать ровно одно значение, которое будет связано с целями в пункте
with
оператораas
, если таковые имеются.В точке, где генератор завершается, выполняется блок, вложенный в оператор
with
. После выхода из блока выполнение генератора возобновляется. Если в блоке возникает необработанное исключение, оно будет повторно вызвано внутри генератора в точке, где произошел выход. Таким образом, вы можете использовать операторtry
…except
…finally
для отлавливания ошибки (если таковая возникла) или обеспечения некоторой очистки. Если исключение поймано только для того, чтобы записать его в журнал или выполнить какое-то действие (а не для того, чтобы полностью подавить его), генератор должен поднять это исключение. В противном случае менеджер контекста генератора укажет операторуwith
, что исключение было обработано, и выполнение возобновится с оператора, следующего сразу за операторомwith
.contextmanager()
используетContextDecorator
, поэтому создаваемые им контекстные менеджеры могут использоваться как декораторы, а также в операторахwith
. При использовании в качестве декоратора при каждом вызове функции неявно создается новый экземпляр генератора (это позволяет «одноразовым» контекстным менеджерам, создаваемымcontextmanager()
, соответствовать требованию, чтобы контекстные менеджеры поддерживали многократные вызовы для использования в качестве декораторов).Изменено в версии 3.2: Использование
ContextDecorator
.
-
@
contextlib.
asynccontextmanager
¶ Аналогично
contextmanager()
, но создает asynchronous context manager.Эта функция decorator может быть использована для определения фабричной функции для асинхронных менеджеров контекста оператора
async with
, без необходимости создания класса или отдельных методов__aenter__()
и__aexit__()
. Она должна быть применена к функции asynchronous generator.Простой пример:
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)¶ Возвращает менеджер контекста, который закрывает что-то по завершении блока. В основном это эквивалентно:
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
Его также можно использовать в качестве замены для asynchronous context managers:
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: Была добавлена поддержка asynchronous context manager.
-
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
Этот менеджер контекста является reentrant.
Добавлено в версии 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
означает, что этот контекстный менеджер не подходит для использования в библиотечном коде и большинстве потоковых приложений. Он также не влияет на вывод подпроцессов. Тем не менее, это все еще полезный подход для многих утилитных скриптов.Этот менеджер контекста является reentrant.
Добавлено в версии 3.4.
-
contextlib.
redirect_stderr
(new_target)¶ Аналогично
redirect_stdout()
, но перенаправляетsys.stderr
на другой файл или файлоподобный объект.Этот менеджер контекста является reentrant.
Добавлено в версии 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 stuff
ContextDecorator
позволяет вместо этого написать:@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__()
контекстного менеджера, и добавляет его непосредственно в стек обратных вызовов.Возвращая истинные значения, эти обратные вызовы могут подавлять исключения так же, как это делают методы контекстного менеджера
__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
¶ asynchronous context manager, похожий на
ExitStack
, который поддерживает объединение синхронных и асинхронных менеджеров контекста, а также имеет корутины для логики очистки.Метод
close()
не реализован, вместо него следует использоватьaclose()
.-
coroutine
enter_async_context
(cm)¶ Аналогичен
enter_context()
, но ожидает асинхронный менеджер контекста.
-
push_async_exit
(exit)¶ Аналогичен
push()
, но ожидает либо асинхронного менеджера контекста, либо корутинной функции.
-
push_async_callback
(callback, /, *args, **kwds)¶ Аналогично
callback()
, но ожидает функцию coroutine.
-
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.
-
coroutine
Примеры и рецепты¶
В этом разделе описаны некоторые примеры и рецепты эффективного использования инструментов, предоставляемых 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
с переменной flag, указывающей, следует ли выполнять тело предложения 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
к другому потоку.
Многократно используемые менеджеры контекста¶
От одноразовых и реентерабельных контекстных менеджеров отличаются «многоразовые» контекстные менеджеры (или, чтобы быть полностью ясным, «многоразовые, но не реентерабельные» контекстные менеджеры, поскольку реентерабельные контекстные менеджеры также являются многоразовыми). Эти контекстные менеджеры поддерживают многократное использование, но будут работать некорректно, если конкретный экземпляр контекстного менеджера уже использовался в операторе containing 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