Spec-Zone.ru › Python 3.5

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

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

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

1. Утилиты

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

@contextlib.contextmanager

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

Простой пример (это не рекомендуется как реальный способ генерации HTML!):

from contextlib import contextmanager

@contextmanager
def tag(name):
    print("<%s>" % name)
    yield
    print("</%s>" % name)

>>> with tag("h1"):
...    print("foo")
...
<h1>
foo
</h1>

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

В момент, когда генератор возвращает значение, блок, вложенный в оператор with, выполняется. Затем генератор возобновляется после выхода из блока. Если произойдет необработанное исключение в блоке, оно будет повторно поднято внутри генератора в той точке, где произошло возвращение значения. Таким образом, вы можете использовать оператор 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.

END_OF_DOCUMENT_MARKER
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()

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

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

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

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

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

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.

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

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

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

from contextlib import contextmanager, ExitStack

class ResourceManager:

    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()

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()

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

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: {}'.format(self.name))

    def __exit__(self, exc_type, exc, exc_tb):
        logging.info('Exiting: {}'.format(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.

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

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 с другим потоком.

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.5/library/contextlib.html

Spec-Zone.ru

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