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__(), и добавляет его напрямую в стек обратных вызовов.Возвращая истинные значения, эти обратные вызовы могут подавлять исключения так же, как это могут делать методы управляющего контекста
__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.13/library/contextlib.html