Spec-Zone.ru › Python 3.7

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. Генератор затем возобновляется после выхода из блока. Если произойдёт необработанное исключение в блоке, оно будет повторно поднято внутри генератора в точке, где произошла выдача значения. Таким образом, вы можете использовать оператор 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('http://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.

END_OF_DOCUMENT_MARKER
contextlib.redirect_stdout(new_target)

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

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

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

f = io.StringIO()
with redirect_stdout(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

Каждый экземпляр сохраняет стек зарегистрированных обратных вызовов, которые вызываются в обратном порядке, когда экземпляр закрывается (явно или неявно в конце оператора 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().

enter_async_context(cm)

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

push_async_exit(exit)

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

push_async_callback(callback, *args, **kwds)

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

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(Callback, self).__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–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.7/library/contextlib.html

Spec-Zone.ru

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