Автор оригинала: Guest Contributor.
Контекстные менеджеры Python
Вступление
Одна из самых “неясных” функций Python, которую используют почти все программисты Python, даже начинающие, но на самом деле не понимают, – это контекстные менеджеры . Вы, вероятно, видели их в виде операторов with
, обычно впервые встречающихся при изучении открытия файлов в Python. Хотя поначалу контекстные менеджеры кажутся немного странными, когда мы действительно погружаемся в них, понимаем мотивацию и методы, стоящие за ними, мы получаем доступ к новому оружию в нашем арсенале программирования. Так что без лишних слов давайте нырнем в него!
Мотивация: Управление ресурсами
Как сказал кто-то гораздо более мудрый, чем я, “Необходимость-мать изобретения”. Чтобы действительно понять, что такое контекстный менеджер и как его можно использовать, мы должны сначала исследовать мотивы, стоящие за ним, — потребности, которые породили это “изобретение”.
Основной мотивацией контекстных менеджеров является управление ресурсами. Когда программа хочет получить доступ к ресурсу на компьютере, она запрашивает его у ОС, а ТА, в свою очередь, предоставляет ей дескриптор этого ресурса. Наиболее распространенными примерами таких ресурсов являются файлы и сетевые порты. Важно понимать, что эти ресурсы имеют ограниченную доступность, например, сетевой порт может использоваться одним процессом одновременно, а количество доступных портов ограничено. Поэтому всякий раз, когда мы открываем ресурс, мы должны помнить закрыть его, чтобы ресурс был освобожден. Но, к сожалению, это легче сказать, чем сделать.
Самый простой способ добиться правильного управления ресурсами-это вызвать функцию close
после того, как мы закончим с ресурсом. Например:
opened_file = open('readme.txt') text = opened_file.read() ... opened_file.close()
Здесь мы открываем файл с именем readme.txt
, читая файл и сохраняя его содержимое в строке text
, а затем, когда мы закончим с ним, закрываем файл, вызывая метод close()
объекта opened_file
. На первый взгляд это может показаться нормальным, но на самом деле это совсем не надежно. Если между открытием и закрытием файла произойдет что-либо непредвиденное, что приведет к тому, что программа не сможет выполнить строку, содержащую оператор close , произойдет утечка ресурсов. Эти неожиданные события
являются тем , что мы называем исключениями , распространенным является случай, когда кто-то принудительно закрывает программу во время ее выполнения.
Теперь правильным способом справиться с этим было бы использовать Обработку исключений , используя try...else
блоки. Посмотрите на следующий пример:
try: opened_file = open('readme.txt') text = opened_file.read() ... else: opened_file.close()
Python всегда следит за тем, чтобы код в блоке else
был выполнен, независимо от того, что может произойти. Именно так программисты на других языках справлялись бы с управлением ресурсами, но программисты на Python получают специальный механизм, который позволяет им реализовать ту же функциональность без всяких шаблонных шаблонов. Именно здесь в игру вступают контекстные менеджеры.
Внедрение Контекстных менеджеров
Теперь, когда мы закончили с самой важной частью понимания контекстных менеджеров, мы можем перейти к их реализации. В этом уроке мы реализуем пользовательский класс File
. Это совершенно излишне, поскольку Python уже предоставляет это, но, тем не менее, это будет хорошее учебное упражнение, поскольку мы всегда сможем вернуться к классу File
, который уже есть в стандартной библиотеке.
Стандартный и “низкоуровневый” способ реализации контекстного менеджера-это определение двух “волшебных” методов в классе, для которого вы хотите реализовать управление ресурсами, __enter__ | и
_ _ exit__|/. Если вы теряетесь, думая: “Что это за волшебный метод? Я никогда не слышал об этом раньше”—ну, если вы начали заниматься объектно-ориентированным программированием на Python, вы наверняка уже сталкивались с волшебным методом, методом __init__
.
За неимением лучших слов, это специальные методы, которые вы можете определить, чтобы сделать ваши классы умнее или добавить к ним “волшебство”. Вы можете найти хороший справочный список всех магических методов, доступных в Python здесь .
В любом случае, возвращаясь к теме, прежде чем мы начнем применять эти два магических метода, мы должны понять их назначение. __enter__
— это метод, который вызывается, когда мы открываем ресурс, или, выражаясь несколько более техническим способом-когда мы “входим” в контекст runtime . Оператор with
свяжет возвращаемое значение этого метода с целевым значением, указанным в предложении as
оператора.
Давайте рассмотрим пример:
class FileManager: def __init__(self, filename): self.filename = filename def __enter__(self): self.opened_file = open(self.filename) return self.opened_file
Как вы можете видеть, метод __enter__
открывает ресурс—файл—и возвращает его. Когда мы используем этот файловый менеджер
в операторе with
, этот метод будет вызван, и его возвращаемое значение будет привязано к целевой переменной, упомянутой вами в предложении as
. Я продемонстрировал это в следующем фрагменте кода:
with FileManager('readme.txt') as file: text = file.read()
Давайте разберем его по частям. Во – первых, экземпляр класса FileManager
создается, когда мы создаем его экземпляр, передавая имя файла “readme.txt-к конструктору. Затем оператор with
начинает работать над ним — он вызывает метод __enter__ | этого объекта
FileManager и присваивает возвращаемое значение переменной
file , упомянутой в предложении
as . Затем внутри блока
with мы можем делать с открытым ресурсом все, что захотим.
Другой важной частью головоломки является метод __exit__
. Метод __exit__
содержит код очистки, который должен быть выполнен после того, как мы закончим с ресурсом, несмотря ни на что. Инструкции в этом методе будут аналогичны инструкциям в блоке else
, которые мы обсуждали ранее при обсуждении обработки исключений. Повторяю, метод __exit__
содержит инструкции по правильному закрытию обработчика ресурсов, чтобы ресурс был освобожден для дальнейшего использования другими программами в ОС.
Теперь давайте посмотрим, как мы могли бы написать этот метод:
class FileManager: def __exit__(self. *exc): self.opened_file.close()
Теперь всякий раз, когда экземпляры этого класса будут использоваться в операторе with
, этот метод __exit__
будет вызываться до того, как программа покинет блок with
, или до того, как программа остановится из-за какого-либо исключения. Теперь давайте посмотрим на весь класс FileManager
, чтобы иметь полное представление.
class FileManager: def __init__(self, filename): self.filename = filename def __enter__(self): self.opened_file = open(self.filename) return self.opened_file def __exit__(self, *exc): self.opened_file.close()
Достаточно просто, не так ли? Мы только что определили действия открытия и очистки в соответствующих магических методах, и Python позаботится об управлении ресурсами везде, где этот класс может быть использован. Это подводит меня к следующей теме-различным способам использования классов context manager, таким как этот File Manager
class.
Использование Контекстных менеджеров
Здесь не так много нужно объяснять, поэтому вместо того, чтобы писать длинные абзацы, я приведу несколько фрагментов кода в этом разделе:
file = FileManager('readme.txt') with file as managed_file: text = managed_file.read() print(text)
with FileManager('readme.txt') as managed_file: text = managed_file.read() print(text)
def open_file(filename): file = FileManager(filename) return file with open_file('readme.txt') as managed_file: text = managed_file.read() print(text)
Вы можете видеть, что главное, что нужно помнить, – это,
- Объект, переданный оператору
with
, должен иметь методы__enter__
и__exit__
. - Метод
__enter__
должен возвращать ресурс, который будет использоваться в блокеwith
.
Важно : Есть некоторые тонкости, которые я опустил, чтобы сделать дискуссию по существу. Для получения точных спецификаций этих магических методов обратитесь к документации Python здесь .
Использование contextlib
Дзен Питона —руководящий принцип Питона как список афоризмов—гласит, что,
Простое лучше, чем сложное.
Чтобы действительно довести эту точку зрения до конца, разработчики Python создали библиотеку с именем contextlib , содержащую утилиты, касающиеся контекстных менеджеров, как будто они недостаточно упростили проблему управления ресурсами. Я собираюсь кратко продемонстрировать здесь только один из них, я рекомендую вам ознакомиться с официальными документами Python для получения дополнительной информации.
from contextlib import contextmanager @contextmanager def open_file(filename): opened_file = open(filename) try: yield opened_file finally: opened_file.close()
Как и в приведенном выше коде, мы можем просто определить функцию, которая дает
s защищенный ресурс в операторе try
, закрывая его в последующем операторе finally
. Другой способ понять это:
- Все содержимое, которое вы иначе поместили бы в метод
__enter__
, за исключением оператораreturn
, идет перед блокомtry
здесь — в основном инструкции по открытию ресурса. - Вместо того чтобы возвращать ресурс, вы
отдаете
его внутри блокаtry
. - Содержимое метода
__exit__
входит в соответствующий блокfinally
.
Как только у нас есть такая функция, мы можем украсить ее с помощью contextlib.contextmanager
decorator, и у нас все хорошо.
with open_file('readme.txt') as managed_file: text = managed_file.read() print(text)
Как вы можете видеть, функция decorated open_file
возвращает контекстный менеджер, и мы можем использовать его напрямую. Это позволяет нам достичь того же эффекта, что и при создании класса FileManager
, без всяких хлопот.
Дальнейшее чтение
Если вы чувствуете энтузиазм и хотите узнать больше о контекстных менеджерах, я рекомендую вам ознакомиться со следующими ссылками:
- Если вы чувствуете энтузиазм и хотите узнать больше о контекстных менеджерах, я рекомендую вам ознакомиться со следующими ссылками:
- Если вы чувствуете энтузиазм и хотите узнать больше о контекстных менеджерах, я рекомендую вам ознакомиться со следующими ссылками:
- Если вы чувствуете энтузиазм и хотите узнать больше о контекстных менеджерах, я рекомендую вам ознакомиться со следующими ссылками:
- Если вы чувствуете энтузиазм и хотите узнать больше о контекстных менеджерах, я рекомендую вам ознакомиться со следующими ссылками: