Документация Python неофициальный перевод

contextlib.md

436 строк · 31.7 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.3/library/contextlib.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 28.6. [`contextlib`](https://python-all.ru/3.3/library/contextlib.html#module-contextlib) – Утилиты для контекстов оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with)89**Исходный код:** [Lib/contextlib.py](https://python-all.ru/src/3.3/Lib/contextlib.py)1011---1213Этот модуль предоставляет утилиты для типичных задач, связанных с оператором [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with). Дополнительную информацию см. также в разделах [*Типы менеджеров контекста*](https://python-all.ru/3.3/library/stdtypes.html#typecontextmanager) и [*Менеджеры контекста оператора with*](https://python-all.ru/3.3/reference/datamodel.html#context-managers).1415## 28.6.1. Утилиты1617Предоставляемые функции и классы:1819#### `@contextlib.contextmanager`2021Эта функция – [*декоратор*](https://python-all.ru/3.3/glossary.html#term-decorator), который можно использовать для определения фабричной функции для менеджеров контекста оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with), без необходимости создавать класс или отдельные методы [`__enter__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__enter__) и [`__exit__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__exit__).2223Простой пример (это не рекомендуется как реальный способ генерации HTML!):2425```python26from contextlib import contextmanager2728@contextmanager29def tag(name):30    print("<%s>" % name)31    yield32    print("</%s>" % name)3334>>> with tag("h1"):35...    print("foo")36...37<h1>38foo39</h1>40```4142Декорируемая функция при вызове должна возвращать [*генератор*](https://python-all.ru/3.3/glossary.html#term-generator)-итератор. Этот итератор должен передать (yield) ровно одно значение, которое будет связано с целями в предложении [`as`](https://python-all.ru/3.3/reference/compound_stmts.html#as) оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with), если оно есть.4344В точке, где генератор выполняет yield, выполняется блок, вложенный в оператор [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with). Затем генератор возобновляется после выхода из блока. Если в блоке возникает необработанное исключение, оно повторно возбуждается внутри генератора в точке, где произошёл yield. Таким образом, можно использовать конструкцию [`try`](https://python-all.ru/3.3/reference/compound_stmts.html#try)...[`except`](https://python-all.ru/3.3/reference/compound_stmts.html#except)...[`finally`](https://python-all.ru/3.3/reference/compound_stmts.html#finally) для перехвата ошибки (если она есть) или для обеспечения выполнения очистки. Если исключение перехватывается лишь для того, чтобы залогировать его или выполнить какое-либо действие (а не подавить его полностью), генератор должен возобновить (reraise) это исключение. В противном случае менеджер контекста-генератор сообщит оператору [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with), что исключение обработано, и выполнение продолжится с оператора, непосредственно следующего за оператором [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with).4546[`contextmanager()`](https://python-all.ru/3.3/library/contextlib.html#contextlib.contextmanager) использует [`ContextDecorator`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ContextDecorator), поэтому создаваемые им менеджеры контекста можно использовать как в качестве декораторов, так и в операторах [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with). При использовании в качестве декоратора новый экземпляр генератора неявно создаётся при каждом вызове функции (это позволяет менеджерам контекста, созданным [`contextmanager()`](https://python-all.ru/3.3/library/contextlib.html#contextlib.contextmanager), которые в противном случае были бы «одноразовыми», удовлетворять требованию, что менеджеры контекста должны поддерживать множественные вызовы, чтобы использоваться в качестве декораторов).4748Изменено в версии 3.2: Использование [`ContextDecorator`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ContextDecorator).4950#### `contextlib.closing(thing)`5152Возвращает контекстный менеджер, который закрывает *thing* после завершения блока. По сути эквивалентно следующему:5354```python55from contextlib import contextmanager5657@contextmanager58def closing(thing):59    try:60        yield thing61    finally:62        thing.close()63```6465И позволяет писать код следующим образом:6667```python68from contextlib import closing69from urllib.request import urlopen7071with closing(urlopen('http://www.python.org')) as page:72    for line in page:73        print(line)74```7576без необходимости явно закрывать `page`. Даже если произойдёт ошибка, `page.close()` будет вызван при выходе из блока [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with).7778#### `class contextlib.ContextDecorator`7980Базовый класс, который позволяет менеджеру контекста также использоваться в качестве декоратора.8182Менеджеры контекста, наследующие от `ContextDecorator`, должны реализовывать `__enter__` и `__exit__` как обычно. `__exit__` сохраняет свою опциональную обработку исключений даже при использовании в качестве декоратора.8384`ContextDecorator` используется [`contextmanager()`](https://python-all.ru/3.3/library/contextlib.html#contextlib.contextmanager), поэтому эта функциональность доступна автоматически.8586Пример `ContextDecorator`:8788```python89from contextlib import ContextDecorator9091class mycontext(ContextDecorator):92    def __enter__(self):93        print('Starting')94        return self9596    def __exit__(self, *exc):97        print('Finishing')98        return False99100>>> @mycontext()101... def function():102...     print('The bit in the middle')103...104>>> function()105Starting106The bit in the middle107Finishing108109>>> with mycontext():110...     print('The bit in the middle')111...112Starting113The bit in the middle114Finishing115```116117Это изменение – всего лишь синтаксический сахар для любой конструкции следующего вида:118119```python120def f():121    with cm():122        # Выполнить действия123```124125`ContextDecorator` позволяет вместо этого написать:126127```python128@cm()129def f():130    # Выполнить действия131```132133Это даёт понять, что `cm` применяется ко всей функции, а не только к её части (и экономия уровня отступа тоже приятна).134135Существующие менеджеры контекста, у которых уже есть базовый класс, могут быть расширены путём использования `ContextDecorator` в качестве примесного класса (mixin class):136137```python138from contextlib import ContextDecorator139140class mycontext(ContextBaseClass, ContextDecorator):141    def __enter__(self):142        return self143144    def __exit__(self, *exc):145        return False146```147148> **Примечание**149>150> Поскольку декорированная функция должна допускать многократный вызов, базовый менеджер контекста должен поддерживать использование в нескольких операторах [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with). Если это не так, то следует использовать исходную конструкцию с явным оператором [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with) внутри функции.151152Новое в версии 3.2.153154#### `class contextlib.ExitStack`155156Менеджер контекста, предназначенный для упрощения программного комбинирования других менеджеров контекста и функций очистки, особенно тех, которые являются необязательными или определяются входными данными.157158Например, набор файлов можно легко обработать в одном операторе with следующим образом:159160```python161with ExitStack() as stack:162    files = [stack.enter_context(open(fname)) for fname in filenames]163    # Все открытые файлы будут автоматически закрыты в конце164    # оператор with, даже если попытки открыть файлы, идущие далее165    # в списке, вызывают исключение166```167168Каждый экземпляр поддерживает стек зарегистрированных колбэков, которые вызываются в обратном порядке при закрытии экземпляра (явно или неявно в конце оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with)). Обратите внимание, что колбэки *не* вызываются неявно при сборке мусора экземпляра стека контекстов.169170Эта модель стека используется, чтобы менеджеры контекста, которые получают свои ресурсы в методе `__init__` (например, файловые объекты), могли быть обработаны корректно.171172Поскольку зарегистрированные колбэки вызываются в обратном порядке регистрации, в итоге это ведёт себя так, как если бы с зарегистрированным набором колбэков использовались несколько вложенных операторов [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with). Это распространяется даже на обработку исключений: если внутренний колбэк подавляет или заменяет исключение, то внешние колбэки получат аргументы на основе этого обновлённого состояния.173174Это относительно низкоуровневый API, который заботится о деталях корректной размотки стека колбэков выхода. Он предоставляет подходящую основу для менеджеров контекста более высокого уровня, которые манипулируют стеком выхода специфичным для приложения образом.175176Новое в версии 3.3.177178#### `enter_context(cm)`179180Входит в новый менеджер контекста и добавляет его метод [`__exit__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__exit__) в стек колбэков. Возвращаемое значение – результат собственного метода менеджера контекста [`__enter__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__enter__).181182Эти менеджеры контекста могут подавлять исключения так же, как если бы они использовались непосредственно как часть оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with).183184#### `push(exit)`185186Добавляет метод [`__exit__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__exit__) менеджера контекста в стек колбэков.187188Поскольку `__enter__` *не* вызывается, этот метод можно использовать, чтобы покрыть часть реализации [`__enter__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__enter__) собственным методом [`__exit__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__exit__) менеджера контекста.189190Если передан объект, не являющийся менеджером контекста, этот метод считает, что это колбэк с той же сигнатурой, что и метод [`__exit__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__exit__) менеджера контекста, и добавляет его напрямую в стек колбэков.191192Возвращая истинные значения, эти колбэки могут подавлять исключения так же, как это делают методы [`__exit__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__exit__) менеджера контекста.193194Переданный объект возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.195196#### `callback(callback, *args, **kwds)`197198Принимает произвольную функцию-колбэк и аргументы и добавляет её в стек колбэков.199200В отличие от других методов, колбэки, добавленные таким образом, не могут подавлять исключения (поскольку им никогда не передаются сведения об исключении).201202Переданный колбэк возвращается из функции, что позволяет использовать этот метод в качестве декоратора функции.203204#### `pop_all()`205206Передаёт стек колбэков новому экземпляру [`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack) и возвращает его. Ни один колбэк при этой операции не вызывается – вместо этого они будут вызваны при закрытии нового стека (явно или неявно в конце оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with)).207208Например, группу файлов можно открыть как операцию «всё или ничего» следующим образом:209210```python211with ExitStack() as stack:212    files = [stack.enter_context(open(fname)) for fname in filenames]213    # Сохраните метод close, но пока не вызывайте его.214    close_files = stack.pop_all().close215    # Если открытие любого файла завершается неудачей, все ранее открытые файлы будут216    # закрыты автоматически. Если все файлы открыты успешно,217    # они останутся открытыми даже после завершения оператора with.218    # close_files() затем можно вызвать явно, чтобы закрыть их все.219```220221#### `close()`222223Немедленно разворачивает стек колбэков, вызывая колбэки в порядке, обратном регистрации. Для любых зарегистрированных менеджеров контекста и колбэков выхода переданные аргументы будут указывать, что исключения не произошло.224225## 28.6.2. Примеры и рецепты226227В этом разделе описаны некоторые примеры и рецепты для эффективного использования инструментов, предоставляемых модулем [`contextlib`](https://python-all.ru/3.3/library/contextlib.html#module-contextlib).228229### 28.6.2.1. Поддержка переменного количества менеджеров контекста230231Основной вариант использования [`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack) описан в документации класса: поддержка переменного числа менеджеров контекста и других операций очистки в одном операторе [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with). Вариативность может быть обусловлена тем, что количество необходимых менеджеров контекста определяется пользовательским вводом (например, открытие указанной пользователем коллекции файлов), или тем, что некоторые менеджеры контекста являются необязательными:232233```python234with ExitStack() as stack:235    for resource in resources:236        stack.enter_context(resource)237    if need_special resource:238        special = acquire_special_resource()239        stack.callback(release_special_resource, special)240    # Выполните операции, использующие полученные ресурсы241```242243Как показано, [`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack) также позволяет довольно легко использовать операторы [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with) для управления произвольными ресурсами, которые изначально не поддерживают протокол управления контекстом.244245### 28.6.2.2. Упрощение поддержки одного опционального менеджера контекста246247В частном случае одного необязательного менеджера контекста экземпляры [`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack) можно использовать как «ничего не делающий» менеджер контекста, что позволяет легко опустить менеджер контекста, не влияя на общую структуру исходного кода:248249```python250def debug_trace(details):251    if __debug__:252        return TraceContext(details)253    # В релизном режиме никаких особых действий с контекстом не выполняется254    return ExitStack()255256with debug_trace():257    # В режиме отладки набор трассируется, в остальных случаях работает обычным образом258```259260### 28.6.2.3. Перехват исключений из методов `__enter__`261262Иногда бывает желательно перехватывать исключения из реализации метода `__enter__`, *не* перехватывая случайно исключения из тела оператора [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with) или метода `__exit__` менеджера контекста. Используя [`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack), можно немного разделить шаги протокола управления контекстом, чтобы добиться этого:263264```python265stack = ExitStack()266try:267    x = stack.enter_context(cm)268except Exception:269    # обработать исключение __enter__270else:271    with stack:272        # Обработка нормального случая273```274275Необходимость в этом, вероятно, указывает на то, что базовый API должен предоставлять прямой интерфейс управления ресурсами для использования с операторами [`try`](https://python-all.ru/3.3/reference/compound_stmts.html#try)/[`except`](https://python-all.ru/3.3/reference/compound_stmts.html#except)/[`finally`](https://python-all.ru/3.3/reference/compound_stmts.html#finally), но не все API хорошо спроектированы в этом отношении. Когда менеджер контекста является единственным предоставляемым API управления ресурсами, [`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack) может упростить обработку различных ситуаций, которые невозможно обработать напрямую в операторе [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with).276277### 28.6.2.4. Очистка в реализации `__enter__`278279Как отмечено в документации [`ExitStack.push()`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack.push), этот метод может быть полезен для очистки уже выделенного ресурса, если последующие шаги в реализации [`__enter__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__enter__) завершатся неудачей.280281Вот пример реализации этого для менеджера контекста, который принимает функции захвата и освобождения ресурса, а также опциональную функцию проверки, и отображает их на протокол управления контекстом:282283```python284from contextlib import contextmanager, ExitStack285286class ResourceManager:287288    def __init__(self, acquire_resource, release_resource, check_resource_ok=None):289        self.acquire_resource = acquire_resource290        self.release_resource = release_resource291        if check_resource_ok is None:292            def check_resource_ok(resource):293                return True294        self.check_resource_ok = check_resource_ok295296    @contextmanager297    def _cleanup_on_error(self):298        with ExitStack() as stack:299            stack.push(self)300            yield301            # Проверка прошла успешно и не вызвала исключение302            # Соответственно, мы хотим сохранить ресурс и передать его303            # обратно вызывающему коду304            stack.pop_all()305306    def __enter__(self):307        resource = self.acquire_resource()308        with self._cleanup_on_error():309            if not self.check_resource_ok(resource):310                msg = "Failed validation for {!r}"311                raise RuntimeError(msg.format(resource))312        return resource313314    def __exit__(self, *exc_details):315        # Нам не нужно дублировать логику освобождения ресурсов316        self.release_resource()317```318319### 28.6.2.5. Замена любого использования `try-finally` и флаговых переменных320321Иногда можно встретить шаблон с оператором `try-finally` и флаговой переменной, указывающей, должно ли выполняться тело пункта `finally`. В своей простейшей форме (которую нельзя обработать простым использованием пункта `except`) это выглядит примерно так:322323```python324cleanup_needed = True325try:326    result = perform_operation()327    if result:328        cleanup_needed = False329finally:330    if cleanup_needed:331        cleanup_resources()332```333334Как и любой код на основе оператора `try`, это может создавать проблемы при разработке и проверке, поскольку код настройки и код очистки могут оказаться разделёнными произвольно длинными участками кода.335336[`ExitStack`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack) позволяет вместо этого зарегистрировать колбэк для выполнения в конце оператора `with`, а затем позже решить пропустить выполнение этого колбэка:337338```python339from contextlib import ExitStack340341with ExitStack() as stack:342    stack.callback(cleanup_resources)343    result = perform_operation()344    if result:345        stack.pop_all()346```347348Это позволяет заранее явно указать предполагаемое поведение очистки, вместо того чтобы требовать отдельную переменную-флаг.349350Если конкретное приложение часто использует этот шаблон, его можно упростить еще больше с помощью небольшого вспомогательного класса:351352```python353from contextlib import ExitStack354355class Callback(ExitStack):356    def __init__(self, callback, *args, **kwds):357        super(Callback, self).__init__()358        self.callback(callback, *args, **kwds)359360    def cancel(self):361        self.pop_all()362363with Callback(cleanup_resources) as cb:364    result = perform_operation()365    if result:366        cb.cancel()367```368369Если очистка ресурса ещё не аккуратно оформлена в виде отдельной функции, всё равно можно использовать форму декоратора [`ExitStack.callback()`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ExitStack.callback), чтобы объявить очистку ресурса заранее:370371```python372from contextlib import ExitStack373374with ExitStack() as stack:375    @stack.callback376    def cleanup_resources():377        ...378    result = perform_operation()379    if result:380        stack.pop_all()381```382383Из-за того, как работает протокол декоратора, функция обратного вызова, объявленная таким образом, не может принимать никаких параметров. Вместо этого любые ресурсы, которые необходимо освободить, должны быть доступны как замыкающие переменные.384385### 28.6.2.6. Использование менеджера контекста в качестве декоратора функции386387[`ContextDecorator`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ContextDecorator) позволяет использовать менеджер контекста как в обычном операторе `with`, так и в качестве декоратора функции.388389Например, иногда бывает полезно обернуть функции или группы операторов в логгер, который может отслеживать время входа и время выхода. Вместо того чтобы писать и декоратор функции, и менеджер контекста для этой задачи, наследование от [`ContextDecorator`](https://python-all.ru/3.3/library/contextlib.html#contextlib.ContextDecorator) предоставляет обе возможности в одном определении:390391```python392from contextlib import ContextDecorator393import logging394395logging.basicConfig(level=logging.INFO)396397class track_entry_and_exit(ContextDecorator):398    def __init__(self, name):399        self.name = name400401    def __enter__(self):402        logging.info('Entering: {}'.format(name))403404    def __exit__(self, exc_type, exc, exc_tb):405        logging.info('Exiting: {}'.format(name))406```407408Экземпляры этого класса можно использовать как менеджер контекста:409410```python411with track_entry_and_exit('widget loader'):412    print('Some time consuming activity goes here')413    load_widget()414```415416А также как декоратор функции:417418```python419@track_entry_and_exit('widget loader')420def activity():421    print('Some time consuming activity goes here')422    load_widget()423```424425Обратите внимание, что есть ещё одно ограничение при использовании менеджеров контекста в качестве декораторов функций: нет способа получить доступ к возвращаемому значению [`__enter__()`](https://python-all.ru/3.3/reference/datamodel.html#object.__enter__). Если это значение необходимо, всё равно нужно использовать явный оператор `with`.426427> **См. также**428>429> **[**PEP 0343**](https://python-all.ru/3.3/library/contextlib.html) – оператор «with»**430>431> Спецификация, предыстория и примеры для оператора432>433> [`with`](https://python-all.ru/3.3/reference/compound_stmts.html#with)434>435> в Python.436