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.Примечание
Большинство типов, управляющих ресурсами, поддерживают протокол менеджера контекста, который закрывает thing при выходе из оператора
with. Поэтомуclosing()наиболее полезен для типов сторонних разработчиков, которые не поддерживают менеджеры контекста. Этот пример приведен только для иллюстрации, так какurlopen()обычно используется в менеджере контекста.
-
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Этот менеджер контекста реентерабельный.
Если код внутри блока
withвызываетBaseExceptionGroup, подавляемые исключения удаляются из группы. Любые исключения из группы, которые не подавлены, повторно поднимаются в новой группе, которая создается с использованием методаderive()исходной группы.Добавлена в версии 3.4.
Изменено в версии 3.12:
suppressтеперь поддерживает подавление исключений, возникающих в рамкахBaseExceptionGroup.
-
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.
-
contextlib.chdir(path) -
Менеджер контекста, не поддерживающий параллельную работу, для изменения текущей рабочей директории. Поскольку это изменяет глобальное состояние, рабочую директорию, он не подходит для большинства многопоточных или асинхронных контекстов. Он также не подходит для большинства нелинейных выполнений кода, таких как генераторы, где выполнение программы временно приостанавливается – если явно не требуется, вы не должны использовать yield, когда этот менеджер контекста активен.
Это простой обертка вокруг
chdir(), она изменяет текущую рабочую директорию при входе и восстанавливает старую при выходе.Этот менеджер контекста реентерабельный.
Добавлена в версии 3.11.
-
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.Изменено в версии 3.11: Возбуждает
TypeErrorвместоAttributeError, если cm не является менеджером контекста.
-
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 -
Менеджер асинхронного контекста, подобный
ExitStack, который поддерживает объединение синхронных и асинхронных менеджеров контекста, а также содержит корутины для логики очистки.Метод
close()не реализован; вместо него необходимо использоватьaclose().-
coroutine enter_async_context(cm) -
Аналогично
ExitStack.enter_context(), но ожидает асинхронного менеджера контекста.Изменено в версии 3.11: Возбуждает
TypeErrorвместоAttributeError, если cm не является асинхронным менеджером контекста.
-
push_async_exit(exit) -
Аналогично
ExitStack.push(), но ожидает либо асинхронного менеджера контекста, либо функцию корутину.
-
push_async_callback(callback, /, *args, **kwds) -
Аналогично
ExitStack.callback(), но ожидает функцию корутину.
-
coroutine aclose() -
Аналогично
ExitStack.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() и chdir(). Вот очень простой пример повторно входящего использования:
>>> 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–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/library/contextlib.html