Spec-Zone.ru › Python 3.6

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

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

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

29.6.1. Утилиты

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

class contextlib.AbstractContextManager

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

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

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

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

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

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

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

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

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

Основное применение 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 для управления произвольными ресурсами, которые не поддерживают протокол управления контекстом по умолчанию.

29.6.2.2. Упрощение поддержки одного необязательного менеджера контекста

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

def debug_trace(details):
    if __debug__:
        return TraceContext(details)
    # Don't do anything special with the context in release mode
    return ExitStack()

with debug_trace():
    # Suite is traced in debug mode, but runs normally otherwise

29.6.2.3. Перехват исключений из методов __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.

29.6.2.4. Очистка в реализации __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()

29.6.2.5. Замена всех случаев использования 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()

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

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

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.

29.6.3. Обработчики контекста однократного, многократного и рекурсивного использования

Большинство обработчиков контекста написаны таким образом, что они могут быть эффективно использованы в инструкции 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

29.6.3.1. Рекурсивные обработчики контекста

Более сложные обработчики контекста могут быть «рекурсивными». Эти обработчики контекста не только могут использоваться в нескольких инструкциях 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 к другому потоку.

29.6.3.2. Многократно используемые обработчики контекста

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

Spec-Zone.ru

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