Как сделать свой контекст менеджер
Ручное закрытие файлов, а также отдача закрытия на откуп среде исполнения, обладают одним существенным недостатком: если между открытием файла на запись и его закрытием произойдёт ошибка, то в лучшем случае файл окажется открыт слишком долго (может остаться ссылка), а в худшем случае не сохранится часть данных.
Хочется иметь возможность автоматически закрывать файл сразу после окончания работы с ним и осуществлять закрытие даже при возникновении ошибки. Файловые объекты уже умеют работать в таком режиме, но для этого их нужно использовать как менеджеры контекста.
Краткое введение в менеджеры контекста
Менеджер контекста (context manager) — это некий объект, реализующий одноимённый протокол (да, кругом протоколы!). Объекты, реализующие этот протокол, позволяют использовать следующий специальный синтаксис:
Весь код в теле with -блока работает "в контексте". Чаще всего контекст подразумевает выделение некоего ресурса, например, файла. По выходу из контекста ресурс автоматически освобождается, даже если при выполнении блока возникло исключение. То, что нам нужно!
Использование файлов как менеджеров контекста
Рассмотрим сразу комплексный пример. Для чего перепишем скрипт нумерации строк файла из предыдущего урока:
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Небольшая заметка о конструкции with и о том как создавать свои менеджеры контекста.
Продолжаю читать Fluent Python и дошла до раздела про менеджеры контекстов.
Конструкция with
Конструкция with используется для оборачивания блока кода менеджером контекста.
Менеджер контекста - это объект, который определяет контекст выполнения операций в конструкции with. Задача менеджера контекста выполнить определенные операции в начале блока with, и в конце.
Чаще всего, менеджер контекста вызывается с помощью блока with, но может использоваться и вызывая его методы напрямую.
Один из самых распространенных примеров использования конструкции with - работа с файлами.
Пример открытия файла:
Именно эта конструкция обычно используется, чтобы открыть файл, что-то сделать с содержимым и затем закрывать файл.
В данном случае, после завершения конструкции with, файл автоматически закроется:
Обратите внимание, что переменная f доступна за пределами конструкции f, так как with не создает отдельного пространства имен, как функция.
Внутри конструкции with происходит следующее:
Метод __enter__ вызывается в самой конструкции with. Если в with есть выражение as , результат выполнения метода, присваивается в переменную, которая указана после as .
Потом выполняются какие-то действия, которые находятся в блоке with.
И в конце вызывается метод __exit__ . В данном случае, этот метод закрывает файл.
В примере с файлом, метод __exit__ гарантирует закрытие файла, независимо от того, было ли исключение в блоке with.
Генератор и @contextlib.contextmanager
В Python существует целый модуль для работы с менеджерами контекста - contextlib.
Например, в этом модуле находится декоратор @contextlib.contextmanager, который позволяет создавать менеджер контекста из генератора.
Генератор должен выдавать только одно значение. При этом то, что находится до yield, будет выполняться в начале блока with. А то, что находится после yield - в конце.
Использование функции lines() выглядит таким образом:
SQLite и with
Выполнение транзакции в блоке with
При работе с SQLite, Python позволяет использовать объект Connection, как менеджер контекста.
Пример использования соединения с базой, как менеджера контекста (файл with_sqlite3_transaction.py):
В блоке with в БД добавляются данные. При этом:
- при возникновении исключения, транзакция автоматически откатывается
- если исключения не было, автоматически выполняется commit
До выполнения скрипта в таблице dhcp такие записи:
Если теперь раскомментировать строку в списке кортежей data, будет возникать исключение, так как MAC-адрес в этой строке совпадает с уже существующим в таблице, а поле mac является primary key и поэтому должно быть уникальным:
Чтобы еще раз попробовать добавить данные, надо раскомментировать строку в списке data и вернуть БД в исходное состояние:
При добавлении данных возникло исключение UNIQUE constraint failed и транзакция откатилась. Поэтому в таблице остались те же записи, которые были до попытки добавить новую информацию.
Содержимое таблицы dhcp до и после добавления информации - одинаково. Это значит, что не записалась ни одна строка из списка data.
Так получилось из-за того, что используется метод executemany и в пределах одной транзакции мы пытаемся записать все 4 строки. Если возникает ошибка с одной из них - откатываются все изменения.
Иногда, это именно то поведение, которое нужно. Если же надо чтобы игнорировались только строки с ошибками, надо использовать метод execute и записывать каждую строку отдельно.
Соединение с БД в блоке with
В sqlite3 with используется только для работы с транзакциями.
И хотя соединение тоже можно открывать в блоке with (файл with_sqlite_conn.py):
На самом деле, после этого блока соединение не закрыто:
Поэтому лучше не открывать соединение таким образом, так как создается впечатление, что оно будет автоматически закрыто.
Но, можно создать свой менеджер контекста, который будет закрывать соединение:
Теперь запрос в блоке try не выполнится, так как соединение уже закрыто:
contextlib.closing
В модуле contextlib есть менеджер контекста closing, который вызывает метод close, в конце блока with.
Соответственно, в предыдущем примере можно не создавать менеджер контекста, а использовать closing (файл with_sqlite3_conn_closing.py):
Результат выглядит аналогично:
Соединение SSH в блоке with
И напоследок еще одна идея для использования менеджера контекста - подключение по SSH с помощью netmiko (файл with_netmiko_contextmanager.py):
Выполнение выглядит так:
Дополнительные материалы
Все примеры выше использовались чтобы показать простейшие примеры использования и заинтересовать этой темой. Ссылки для погружения в тему ниже.
Конструкция with . as используется для оборачивания выполнения блока инструкций менеджером контекста. Иногда это более удобная конструкция, чем try. except. finally.
Синтаксис конструкции with . as:
Теперь по порядку о том, что происходит при выполнении данного блока:
- Выполняется выражение в конструкции with . as.
- Загружается специальный метод __exit__ для дальнейшего использования.
- Выполняется метод __enter__. Если конструкция with включает в себя слово as, то возвращаемое методом __enter__ значение записывается в переменную.
- Выполняется suite.
- Вызывается метод __exit__, причём неважно, выполнилось ли suite или произошло исключение. В этот метод передаются параметры исключения, если оно произошло, или во всех аргументах значение None, если исключения не было.
Если в конструкции with - as было несколько выражений, то это эквивалентно нескольким вложенным конструкциям:
Для чего применяется конструкция with . as? Для гарантии того, что критические функции выполнятся в любом случае. Самый распространённый пример использования этой конструкции - открытие файлов. Я уже рассказывал об открытии файлов с помощью функции open, однако конструкция with . as, как правило, является более удобной и гарантирует закрытие файла в любом случае.
Гарантирует, что файл будет закрыт вне зависимости от того, что введёт пользователь.
В эти дни я трачу гораздо больше времени на написание и анализ кода на Python, чем на PHP. Это было освежающее изменение темпа, и интересно изучать различные паттерны, представленные на разных языках программирования.
Если вы не нашли время осмотреться и посмотреть, что делают другие языки или фреймворки, я настоятельно рекомендую это сделать.
Одним из лучших шаблонов в Python является Context Manager. В Python определенные объекты и функции могут быть заключены в блок with, используемый для предоставления определенного контекста к коду, выполняющемуся внутри него. Например, открытие файлов становится невероятно простым:
Прелесть этого шаблона заключается в автоматической очистке ресурсов. Вам не нужно управлять указателями файлов вручную. Вам не нужно инициализировать и отключать сеанс базы данных. Менеджер контекста обрабатывает вещи для вас автоматически.
Менеджер контекста базы данных может управлять созданием и закрытием соединения с самой базой данных (или с используемым пулом соединений). Или его можно использовать для переноса транзакции и автоматической фиксации результатов в конце блока. В любом случае, вам не нужно управлять контекстом самостоятельно.
PHP не имеет такого шаблона изначально. Я думаю, что добавление конструкции with было бы огромной прибавкой для языка, но мои навыки кодирования на более низком уровне немного устарели, чтобы написать быстрое подтверждение концепции. Вместо этого я могу использовать генераторы и циклы для имитации аналогичных функций с существующей системой.
Приведенный выше пример Python использовал функцию open() для демонстрации управления контекстом применительно к файлам. Мы можем сделать нечто подобное в PHP с помощью следующей вспомогательной функции:
Поскольку эта функция является генератором, мы можем использовать ее изначально внутри цикла foreach.
Поскольку генератор возвращает только один yield , мы получаем только один элемент для итерации в нашем цикле foreach. Цикл также автоматически вернется к генератору после итерации, что дает нам возможность очистить наш контекст и область видимости.
Это немного более многословно, чем в Python, и нам нужно создавать свои собственные менеджеры контекста, но это гораздо более чистый способ убедиться, что мы убираем за собой.
Делая шаг вперед, предположим, что нам нужно работать с соединением PDO. Мы хотим подключиться к базе данных, выполнить некоторую работу в транзакции и автоматически зафиксировать результат. Это выглядело бы примерно так:
Та же самая операция, использующая шаблон менеджера контекста, была бы намного проще:
Это сложнее, чем в первом примере. Суть в том, что код установки и разрыва соединения с базой данных и транзакции происходит вне вашей бизнес-логики. Здесь функция transaction() может находиться в отдельном файле или пространстве имен от остальной части вашего кода, что обеспечивает чистоту логики выполнения и избавляет от необходимости беспокоиться о настройке / завершении работы.
Файлы, базы данных, удаленные подключения к ресурсам, блокировки, потоки. Это все интенсивные операции, которые могут извлечь выгоду из использования этого шаблона – и которые используют этот шаблон в Python и других языках.
Это шаблон, который будет иметь значение в вашем коде? Это, конечно, отличается от того, как многие из нас пишут PHP сегодня. Как еще можно использовать эту модель? Было бы более разумно, если бы мы расширили язык с помощью нашей собственной реализации with?
Я не понимаю, что такое контекст with . as в python. Я знаю несколько паттернов, в рамках которых его можно использовать. При этом, я не понимаю тонкостей этой конструкции. В качестве ответа на вопрос я бы хотел увидеть, как контекст with . as выражается в базовых конструкциях в python (т.е. можно ли его реализовать через циклы, функции, условия, присваивания и т.п.).
Другим вариантом ответа на вопрос будет описание проблемы, которую решает with . as .
Приведу пример: декоратор -- это функция, которая возвращает функцию. Сказав эту фразу, можно легко воспроизвести "нативный декоратор". С контекстом я такой интерпретации не знаю.
Там слишком мало чтоб написать простой ответ и слишком лень чтоб писать подробный) поставлю вам плюсик
Читайте также: