Spec-Zone.ru › Python 3.11

contextlib — Утилиты для контекстов оператора with

Исходный код: Lib/contextlib.py

Этот модуль предоставляет утилиты для распространённых задач, связанных с оператором with. Для получения дополнительной информации см. также Типы контекстных менеджеров и Контекстные менеджеры оператора With.

Утилиты

Предоставленные функции и классы:

class contextlib.AbstractContextManager

Абстрактный базовый класс для классов, реализующих абстрактный базовый класс object.__enter__() и object.__exit__(). Предоставляется реализация по умолчанию для object.__enter__(), которая возвращает self, в то время как object.__exit__() является абстрактным методом, который по умолчанию возвращает None. См. также определение Типов менеджеров контекста.

Новое в версии 3.6.

class contextlib.AbstractAsyncContextManager

Абстрактный базовый класс для классов, реализующих абстрактный базовый класс object.__aenter__() и object.__aexit__(). Предоставляется реализация по умолчанию для object.__aenter__(), которая возвращает self, в то время как object.__aexit__() является абстрактным методом, который по умолчанию возвращает None. См. также определение Асинхронных менеджеров контекста.

Новое в версии 3.7.

@contextlib.contextmanager

Эта функция является декоратором, который может использоваться для определения функции-фабрики для менеджеров контекста оператора with без необходимости создания класса или отдельных методов __enter__() и __exit__().

Хотя многие объекты изначально поддерживают использование в операторах with, иногда необходимо управлять ресурсом, который сам по себе не является менеджером контекста и не реализует метод close() для использования с contextlib.closing.

Абстрактный пример показан ниже, чтобы обеспечить правильное управление ресурсами:

from contextlib import contextmanager

@contextmanager
def managed_resource(*args, **kwds):
    # Code to acquire resource, e.g.:
    resource = acquire_resource(*args, **kwds)
    try:
        yield resource
    finally:
        # Code to release resource, e.g.:
        release_resource(resource)

Функцию можно использовать следующим образом:

>>> with managed_resource(timeout=3600) as resource:
...     # Resource is released at the end of this block,
...     # even if code in the block raises an exception

Декорируемая функция должна возвращать генератор-итератор при вызове. Этот итератор должен вернуть ровно одно значение, которое будет привязано к целевым переменным в as разделе оператора with, если таковой имеется.

В момент, когда генератор возвращает значение, блок, вложенный в оператор with, выполняется. Генератор возобновляется после выхода из блока. Если в блоке произойдёт необработанное исключение, оно повторно возникает внутри генератора в точке, где было выполнено yield. Таким образом, вы можете использовать оператор try…except…finally, чтобы перехватить ошибку (если таковая имеется) или гарантировать выполнение некоторой очистки. Если исключение перехватывается только для того, чтобы его записать в журнал или выполнить некоторое действие (а не для того, чтобы полностью подавить его), генератор должен повторно возбудить это исключение. В противном случае менеджер контекста генератора укажет оператору with на то, что исключение обработано, и выполнение продолжится со следующей за оператором with строкой.

contextmanager() использует ContextDecorator, поэтому созданные менеджеры контекста могут использоваться как декораторы, так и в операторах with. При использовании в качестве декоратора новый экземпляр генератора неявно создаётся при каждом вызове функции (это позволяет менеджерам контекста, созданным contextmanager(), удовлетворять требованию, что менеджеры контекста поддерживают несколько вызовов для использования в качестве декораторов).

Изменено в версии 3.2: Использование ContextDecorator.

@contextlib.asynccontextmanager

Аналогично contextmanager(), но создаёт асинхронный менеджер контекста.

Эта функция является декоратором, который может использоваться для определения функции-фабрики для асинхронных менеджеров контекста оператора async with, без необходимости создания класса или отдельных методов __aenter__() и __aexit__(). Она должна применяться к асинхронной функции-генератору.

Простой пример:

from contextlib import asynccontextmanager

@asynccontextmanager
async def get_connection():
    conn = await acquire_db_connection()
    try:
        yield conn
    finally:
        await release_db_connection(conn)

async def get_all_users():
    async with get_connection() as conn:
        return conn.query('SELECT ...')

Новое в версии 3.7.

Менеджеры контекста, определённые с помощью asynccontextmanager(), могут использоваться как декораторы или с операторами async with:

import time
from contextlib import asynccontextmanager

@asynccontextmanager
async def timeit():
    now = time.monotonic()
    try:
        yield
    finally:
        print(f'it took {time.monotonic() - now}s to run')

@timeit()
async def main():
    # ... async code ...

При использовании в качестве декоратора новый экземпляр генератора неявно создаётся при каждом вызове функции. Это позволяет асинхронным менеджерам контекста, созданным с помощью asynccontextmanager(), удовлетворять требованию, что менеджеры контекста поддерживают несколько вызовов для использования в качестве декораторов.

Изменено в версии 3.10: Асинхронные менеджеры контекста, созданные с помощью asynccontextmanager(), могут использоваться как декораторы.

contextlib.closing(thing)

Возвращает менеджер контекста, который закрывает thing по завершении блока. Это в основном эквивалентно:

from contextlib import contextmanager

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

И позволяет вам писать код так:

from contextlib import closing
from urllib.request import urlopen

with closing(urlopen('https://www.python.org')) as page:
    for line in page:
        print(line)

без необходимости явно закрывать page. Даже если произойдёт ошибка, page.close() будет вызван при выходе из блока with.

contextlib.aclosing(thing)

Возвращает асинхронный менеджер контекста, который вызывает метод aclose() объекта thing по завершении блока. Это в основном эквивалентно:

from contextlib import asynccontextmanager

@asynccontextmanager
async def aclosing(thing):
    try:
        yield thing
    finally:
        await thing.aclose()

Важно, что aclosing() поддерживает детерминированную очистку асинхронных генераторов, когда они завершаются преждевременно с помощью break или исключения. Например:

from contextlib import aclosing

async with aclosing(my_generator()) as values:
    async for value in values:
        if value == 42:
            break

Эта модель гарантирует, что код асинхронного выхода генератора выполняется в том же контексте, что и его итерации (так что исключения и переменные контекста работают как ожидается, а код выхода не выполняется после окончания срока службы какой-либо задачи, от которой он зависит).

Новое в версии 3.10.

contextlib.nullcontext(enter_result=None)

Возвращает менеджер контекста, возвращающий enter_result из __enter__, но при этом ничего не делает. Он предназначен для использования в качестве плацехолдера для необязательного менеджера контекста, например:

def myfunction(arg, ignore_exceptions=False):
    if ignore_exceptions:
        # Use suppress to ignore all exceptions.
        cm = contextlib.suppress(Exception)
    else:
        # Do not ignore any exceptions, cm has no effect.
        cm = contextlib.nullcontext()
    with cm:
        # Do something

Пример использования enter_result:

def process_file(file_or_path):
    if isinstance(file_or_path, str):
        # If string, open file
        cm = open(file_or_path)
    else:
        # Caller is responsible for closing file
        cm = nullcontext(file_or_path)

    with cm as file:
        # Perform processing on the file

Он также может использоваться в качестве плацехолдера для асинхронных менеджеров контекста:

async def send_http(session=None):
    if not session:
        # If no http session, create it with aiohttp
        cm = aiohttp.ClientSession()
    else:
        # Caller is responsible for closing the session
        cm = nullcontext(session)

    async with cm as session:
        # Send http requests with session

Новое в версии 3.7.

Изменено в версии 3.10: Добавлена поддержка асинхронных менеджеров контекста.

contextlib.suppress(*exceptions)

Возвращает менеджер контекста, который подавляет любые из указанных исключений, если они возникают в теле оператора with и затем возобновляет выполнение с первой инструкцией после конца оператора with.

Как и в случае с любым другим механизмом, полностью подавляющим исключения, этот менеджер контекста следует использовать только для обработки очень специфических ошибок, где известно, что беспрепятственное продолжение выполнения программы — правильное решение.

Например:

from contextlib import suppress

with suppress(FileNotFoundError):
    os.remove('somefile.tmp')

with suppress(FileNotFoundError):
    os.remove('someotherfile.tmp')

Этот код эквивалентен:

try:
    os.remove('somefile.tmp')
except FileNotFoundError:
    pass

try:
    os.remove('someotherfile.tmp')
except FileNotFoundError:
    pass

Этот менеджер контекста реентерабелен.

Введено в версии 3.4.

contextlib.redirect_stdout(new_target)

Менеджер контекста для временного перенаправления sys.stdout в другой файл или файлоподобный объект.

Этот инструмент добавляет гибкость существующим функциям или классам, чьи вывод жёстко привязан к stdout.

Например, вывод help() обычно отправляется в sys.stdout. Вы можете захватить этот вывод в строку, перенаправив вывод в объект io.StringIO. Заменяемый поток возвращается методом __enter__ и, таким образом, доступен как целевой объект оператора with:

with redirect_stdout(io.StringIO()) as f:
    help(pow)
s = f.getvalue()

Чтобы направить вывод help() в файл на диске, перенаправьте вывод в обычный файл:

with open('help.txt', 'w') as f:
    with redirect_stdout(f):
        help(pow)

Чтобы направить вывод help() в sys.stderr:

with redirect_stdout(sys.stderr):
    help(pow)

Обратите внимание, что глобальное побочное действие на sys.stdout означает, что этот менеджер контекста не подходит для использования в библиотечном коде и большинстве многопоточных приложений. Он также не влияет на вывод дочерних процессов. Однако это по-прежнему полезный подход для многих утилитарных скриптов.

Этот менеджер контекста реентерабелен.

Введено в версии 3.4.

contextlib.redirect_stderr(new_target)

Аналогично redirect_stdout(), но перенаправляет sys.stderr в другой файл или файлоподобный объект.

Этот менеджер контекста реентерабелен.

Введено в версии 3.5.

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 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.

Изменено в версии 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 .

См. также

PEP 343 - Оператор «with»

Спецификация, контекст и примеры для оператора Python 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–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/library/contextlib.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API