Менеджеры контекста Python

Введение Одна из самых «непонятных» функций Python, которую используют почти все программисты Python, даже начинающие, но не очень понимающие, - это менеджеры контекста. Вы, наверное, видели их в форме операторов with, которые обычно впервые встречаются, когда вы изучаете открытие файлов в 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 , произойдет утечка ресурсов. Эти неожиданные события - это то, что мы называем exceptions , обычно случаются, когда кто-то принудительно закрывает программу во время ее выполнения.

Теперь правильный способ справиться с этим - использовать обработку исключений с использованием блоков 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__ - это метод, который вызывается, когда мы открываем ресурс, или, говоря несколько более технически, - когда мы «входим» в контекст выполнения . Оператор 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__ открывает ресурс - файл - и возвращает его. Когда мы используем этот FileManager в операторе 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 позаботится об управлении ресурсами, где бы этот класс ни использовался. Это подводит меня к следующей теме, различным способам использования классов диспетчера контекста, таких как этот класс FileManager

Использование менеджеров контекста

Здесь особо нечего объяснять, поэтому вместо того, чтобы писать длинные абзацы, я приведу несколько фрагментов кода в этом разделе:

 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) 

Как видите, главное помнить:

  1. Объект, переданный в with должен иметь __enter__ и __exit__ .
  2. Метод __enter__ должен возвращать ресурс, который будет использоваться в блоке with

Важно : есть некоторые тонкости, которые я упустил, чтобы обсуждение было предметным. Точные спецификации этих волшебных методов см. В документации Python здесь .

Использование contextlib

Дзен Python - руководящий принцип Python в виде списка афоризмов - гласит, что:

Лучше простое, чем сложное.

Чтобы действительно понять этот момент, разработчики Python создали библиотеку с именем contextlib, содержащую утилиты для менеджеров контекста, как будто они недостаточно упростили проблему управления ресурсами. Я собираюсь кратко продемонстрировать здесь только один из них, я рекомендую вам ознакомиться с официальной документацией Python, чтобы узнать больше.

 from contextlib import contextmanager 
 
 @contextmanager 
 def open_file(filename): 
 opened_file = open(filename) 
 try: 
 yield opened_file 
 finally: 
 opened_file.close() 

Как и в приведенном выше коде, мы можем просто определить функцию, которая yield защищенный ресурс в операторе try , закрывая его в последующем операторе finally Другой способ понять это:

  • Все содержимое, которое вы в противном случае поместили бы в __enter__ , за исключением return , находится try - в основном это инструкции по открытию ресурса.
  • Вместо того, чтобы возвращать ресурс, вы yield его внутри блока try
  • Содержимое __exit__ внутри соответствующего блока finally

Когда у нас есть такая функция, мы можем украсить ее с contextlib.contextmanager и все в порядке.

 with open_file('readme.txt') as managed_file: 
 text = managed_file.read() 
 print(text) 

Как видите, декорированная open_file возвращает диспетчер контекста, и мы можем использовать его напрямую. Это позволяет без лишних хлопот достичь того же эффекта, что и при создании FileManager

дальнейшее чтение

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

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus

Содержание