Spec-Zone.ru › Python 3.9

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.

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

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

contextlib.suppress(*exceptions)

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

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

Например:

from contextlib import suppress

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

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

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

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

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

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

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

contextlib.redirect_stdout(new_target)

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

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

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

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

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

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

Для отправки вывода help() в sys.stderr:

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

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

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

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

contextlib.redirect_stderr(new_target)

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

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

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

class contextlib.ContextDecorator

Базовый класс, который позволяет менеджеру контекста также использоваться в качестве декоратора.

Менеджеры контекста, наследующие от ContextDecorator, должны реализовывать __enter__ и __exit__ как обычно. __exit__ сохраняет свою необязательную обработку исключений даже при использовании в качестве декоратора.

ContextDecorator используется contextmanager(), поэтому вы получаете эту функциональность автоматически.

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

from contextlib import ContextDecorator

class mycontext(ContextDecorator):
    def __enter__(self):
        print('Starting')
        return self

    def __exit__(self, *exc):
        print('Finishing')
        return False

>>> @mycontext()
... def function():
...     print('The bit in the middle')
...
>>> function()
Starting
The bit in the middle
Finishing

>>> with mycontext():
...     print('The bit in the middle')
...
Starting
The bit in the middle
Finishing

Это изменение — просто синтаксический сахар для любого конструкта следующего вида:

def f():
    with cm():
        # Do 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.ExitStack

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

Например, множество файлов легко можно обработать в одном операторе with следующим образом:

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # All opened files will automatically be closed at the end of
    # the with statement, even if attempts to open files later
    # in the list raise an exception

Метод __enter__() возвращает экземпляр ExitStack и не выполняет дополнительных операций.

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

Данная модель стека используется, чтобы менеджеры контекста, которые приобретают свои ресурсы в методе __init__ (например, объекты файлов), могли обрабатываться правильно.

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

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

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

enter_context(cm)

Входит в новый менеджер контекста и добавляет его метод __exit__() в стек обратных вызовов. Возвращаемое значение — результат собственного метода менеджера контекста __enter__().

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

push(exit)

Добавляет метод __exit__() менеджера контекста в стек обратных вызовов.

Так как __enter__ не вызывается, этот метод может использоваться для покрытия части реализации __enter__() собственным методом менеджера контекста __exit__().

Если передан объект, который не является менеджером контекста, этот метод предполагает, что это обратный вызов с той же подписью, что и метод __exit__() менеджера контекста, и добавляет его непосредственно в стек обратных вызовов.

Возвращая значения true, эти обратные вызовы могут подавлять исключения так же, как это могут делать методы менеджера контекста __exit__().

Переданный объект возвращается из функции, что позволяет использовать этот метод в качестве декоратора функций.

callback(callback, /, *args, **kwds)

Принимает произвольную функцию обратного вызова и аргументы и добавляет её в стек обратных вызовов.

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

Переданный обратный вызов возвращается из функции, что позволяет использовать этот метод в качестве декоратора функций.

pop_all()

Переносит стек обратных вызовов в новый экземпляр ExitStack и возвращает его. Никакие обратные вызовы не вызываются в ходе этой операции — вместо этого они будут вызваны при закрытии нового стека (явном или неявном в конце оператора with).

Например, группу файлов можно открыть как операцию «все или ничего» следующим образом:

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # Hold onto the close method, but don't call it yet.
    close_files = stack.pop_all().close
    # If opening any file fails, all previously opened files will be
    # closed automatically. If all files are opened successfully,
    # they will remain open even after the with statement ends.
    # close_files() can then be invoked explicitly to close them all.
close()

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

class contextlib.AsyncExitStack

Асинхронный менеджер контекста, аналогичный ExitStack, поддерживающий комбинирование синхронных и асинхронных менеджеров контекста, а также имеющий корутины для логики очистки.

Метод close() не реализован, вместо него следует использовать aclose().

coroutine enter_async_context(cm)

Аналогично enter_context() но ожидает асинхронного менеджера контекста.

push_async_exit(exit)

Аналогично push() но ожидает либо асинхронного менеджера контекста, либо функцию-корутину.

push_async_callback(callback, /, *args, **kwds)

Аналогично callback() но ожидает функцию-корутину.

coroutine aclose()

Аналогично close() но корректно обрабатывает awaitables.

Продолжая пример для asynccontextmanager():

async with AsyncExitStack() as stack:
    connections = [await stack.enter_async_context(get_connection())
        for i in range(5)]
    # All opened connections will automatically be released at the end of
    # the async with statement, even if attempts to open a connection
    # later in the list raise an exception.

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

Примеры и рецепты

В этом разделе описаны некоторые примеры и рецепты эффективного использования инструментов, предоставляемых contextlib.

Поддержка переменного числа менеджеров контекста

Основной случай использования ExitStack — это тот, который приведен в документации к классу: поддержка переменного числа менеджеров контекста и других операций очистки в одном операторе with. Изменчивость может исходить из количества необходимых менеджеров контекста, определяемых пользователем (например, открытие указанного пользователем набора файлов) или от того, что некоторые менеджеры контекста являются необязательными:

with ExitStack() as stack:
    for resource in resources:
        stack.enter_context(resource)
    if need_special_resource():
        special = acquire_special_resource()
        stack.callback(release_special_resource, special)
    # Perform operations that use the acquired resources

Как показано, ExitStack также делает довольно простым использование операторов with для управления произвольными ресурсами, которые не поддерживают протокол управления контекстом по умолчанию.

Перехват исключений из методов __enter__

Иногда желательно перехватывать исключения из реализации метода __enter__, не непреднамеренно перехватывая исключения из тела оператора with или метода __exit__ менеджера контекста. Используя ExitStack, шаги в протоколе управления контекстом можно немного разделить, чтобы это позволить:

stack = ExitStack()
try:
    x = stack.enter_context(cm)
except Exception:
    # handle __enter__ exception
else:
    with stack:
        # Handle normal case

В действительности, необходимость сделать это, вероятно, указывает на то, что базовый API должен предоставлять интерфейс прямого управления ресурсами для использования с операторами try/except/finally, но не все API хорошо спроектированы в этом отношении. Когда менеджер контекста является единственным API управления ресурсами, предоставляемым, тогда ExitStack может упростить обработку различных ситуаций, которые не могут быть обработаны непосредственно в операторе with.

Очистка в реализации __enter__

Как отмечено в документации ExitStack.push(), этот метод может быть полезен при очистке уже выделенного ресурса, если последующие шаги в реализации __enter__() терпят неудачу.

Вот пример выполнения этого для менеджера контекста, который принимает функции получения и освобождения ресурсов, а также необязательную функцию проверки и сопоставляет их с протоколом управления контекстом:

from contextlib import contextmanager, AbstractContextManager, ExitStack

class ResourceManager(AbstractContextManager):

    def __init__(self, acquire_resource, release_resource, check_resource_ok=None):
        self.acquire_resource = acquire_resource
        self.release_resource = release_resource
        if check_resource_ok is None:
            def check_resource_ok(resource):
                return True
        self.check_resource_ok = check_resource_ok

    @contextmanager
    def _cleanup_on_error(self):
        with ExitStack() as stack:
            stack.push(self)
            yield
            # The validation check passed and didn't raise an exception
            # Accordingly, we want to keep the resource, and pass it
            # back to our caller
            stack.pop_all()

    def __enter__(self):
        resource = self.acquire_resource()
        with self._cleanup_on_error():
            if not self.check_resource_ok(resource):
                msg = "Failed validation for {!r}"
                raise RuntimeError(msg.format(resource))
        return resource

    def __exit__(self, *exc_details):
        # We don't need to duplicate any of our resource release logic
        self.release_resource()

Замена любого использования try-finally и флаговых переменных

Иногда встречается шаблон оператора try-finally с флаговой переменной для обозначения того, должно ли тело условия finally выполняться. В простейшем виде (который уже можно обработать, просто используя вместо этого условие except), он выглядит примерно так:

cleanup_needed = True
try:
    result = perform_operation()
    if result:
        cleanup_needed = False
finally:
    if cleanup_needed:
        cleanup_resources()

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

ExitStack позволяет вместо этого зарегистрировать обратный вызов для выполнения в конце оператора with, а затем позже решить пропустить выполнение этого обратного вызова:

from contextlib import ExitStack

with ExitStack() as stack:
    stack.callback(cleanup_resources)
    result = perform_operation()
    if result:
        stack.pop_all()

Это позволяет сделать поведение очистки, которое требуется, явным с самого начала, а не требует отдельной флаговой переменной.

Если приложение часто использует этот шаблон, его можно упростить еще больше с помощью небольшого вспомогательного класса:

from contextlib import ExitStack

class Callback(ExitStack):
    def __init__(self, callback, /, *args, **kwds):
        super().__init__()
        self.callback(callback, *args, **kwds)

    def cancel(self):
        self.pop_all()

with Callback(cleanup_resources) as cb:
    result = perform_operation()
    if result:
        cb.cancel()

Если очистка ресурса не объединена в отдельную функцию, то по-прежнему можно использовать декораторную форму ExitStack.callback() для объявления очистки ресурса заранее:

from contextlib import ExitStack

with ExitStack() as stack:
    @stack.callback
    def cleanup_resources():
        ...
    result = perform_operation()
    if result:
        stack.pop_all()

Из-за того, как работает протокол декоратора, функция обратного вызова, объявленная таким образом, не может принимать какие-либо параметры. Вместо этого к любым ресурсам, которые нужно освободить, необходимо обращаться как к переменным закрытия.

Использование менеджера контекста в качестве декоратора функции

ContextDecorator позволяет использовать менеджер контекста как в обычном операторе with, так и в качестве декоратора функции.

Например, иногда полезно обернуть функции или группы инструкций логгером, который может отслеживать время входа и время выхода. Вместо того, чтобы писать как декоратор функции, так и менеджер контекста для этой задачи, наследование от ContextDecorator предоставляет обе возможности в одном определении:

from contextlib import ContextDecorator
import logging

logging.basicConfig(level=logging.INFO)

class track_entry_and_exit(ContextDecorator):
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        logging.info('Entering: %s', self.name)

    def __exit__(self, exc_type, exc, exc_tb):
        logging.info('Exiting: %s', self.name)

Экземпляры этого класса могут быть использованы как менеджер контекста:

with track_entry_and_exit('widget loader'):
    print('Some time consuming activity goes here')
    load_widget()

И также как декоратор функции:

@track_entry_and_exit('widget loader')
def activity():
    print('Some time consuming activity goes here')
    load_widget()

Обратите внимание, что существует одно дополнительное ограничение при использовании менеджеров контекста в качестве декораторов функций: нет способа получить значение возврата __enter__(). Если это значение нужно, то все равно необходимо использовать явный оператор with.

См. также

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(). Вот очень простой пример рекурсивного использования:

>>> 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–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.9/library/contextlib.html

Spec-Zone.ru

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