Блог питониста
Данная статья — перевод c моим небольшим дополнением, оригинал.
Часто ключевое слово with не до конца понятно даже опытным разработчикам.
Как и многие другие вещи в Python, ключевое слово with на самом деле очень просто устроено, это станет очевидно, как только вы поймете какую проблему оно решает. Посмотрите на данный код:
1 set things up 2 try: 3 do something 4 finally: 5 tear things down
Здесь под «set things up» подразумевается открытие файла, подключение какого-то внешнего ресурса, а под «tear things down» — закрытие файла, отключение от внешнего ресурса. Конструкция try-finally гарантирует, что «tear things down» часть будет всегда исполнена, даже если код, делающий что-либо вызовет ошибку или не завершится.
Если это часто ипользуется, то было бы удобно вынести код “set things up” и “tear things down” в библиотечную функцию, чтобы легко ее использовать. Конечно, вы можете сделать что-то вроде:
1 def controlled_execution(callback): 2 set things up 3 try: 4 callback(thing) 5 finally: 6 tear things down 7 8 def my_function(thing): 9 do something 10 11 controlled_execution(my_function)
Но это немного многословно, особенно если вам нужно изменять локальные переменные. Другой подход заключается в использовании генератора, а затем нужно использовать for-in:
1 def controlled_execution(): 2 set things up 3 try: 4 yield thing 5 finally: 6 tear things down 7 8 for thing in controlled_execution(): 9 do something with thing
Но yield нельзя было использовать внутри try-finally в 2.4 и раньше. И немного странно использовать loop для чего-то, что вы хотите выполнить один раз.
Поэтому, после рассмотрения нескольких вариантов, Гвидо Ван Россум и python-dev команда наконец решили использовать объект вместо генератора, чтобы контролировать поведение данного кода:
1 class controlled_execution: 2 def __enter__(self): 3 set things up 4 return thing 5 def __exit__(self, type, value, traceback): 6 tear things down 7 8 with controlled_execution() as thing: 9 some code
Теперь, когда «with» выражение исполняется, Python исполняет выражение, вызывает метод __enter__ с полученным значением (которое называется «context guard»), затем присваивает переменной переданной словом as (в данном случае thing) то, что возвращает метод __enter__. Далее, Python исполняет тело (в данное случае some code), и в любом случае вызывает метод __exit__.
В добавок, __exit__ может подавить исключение, вернуть вместо него True. Например, этот __exit__ заменяет TypeError, но разрешает все другие исключения:
1 def __exit__(self, type, value, traceback): 2 return isinstance(value, TypeError)
1 class controlled_execution: 2 def __enter__(self): 3 print('in enter') 4 print(self) 5 return ('test') 6 7 def __exit__(self, type, value, traceback): 8 print('in exit') 9 10 11 with controlled_execution() as thing: 12 print('in here') 13 print(thing) 14 raise TypeError
alex@vostro:~/projects/blog_materials$ python test.py in enter in here test in exit Traceback (most recent call last): File "test.py", line 14, in raise TypeError TypeError
1 class controlled_execution: 2 def __enter__(self): 3 print('in enter') 4 print(self) 5 return ('test') 6 7 def __exit__(self, type, value, traceback): 8 print('in exit') 9 return isinstance(value, TypeError) 10 11 12 with controlled_execution() as thing: 13 print('in here') 14 print(thing) 15 raise TypeError
alex@vostro:~/projects/blog_materials$ python test.py in enter in here test in exit
В Python 2.5 у объекта типа file появились методы __enter__ и __exit__, первый просто возвращает сам объект, а второй закрывает файл:
>>> f = open("x.txt") >>> f >>> f.__enter__() >>> f.read(1) 'X' >>> f.__exit__(None, None, None) >>> f.read(1) Traceback (most recent call last): File "", line 1, in ValueError: I/O operation on closed file
Поэтому, чтобы открыть файл, сделать что-то с содержимым и точно закрыть его, вы просто делаете так:
1 with open("x.txt") as f: 2 data = f.read() 3 do something with data
Это не было очень сложно, правда?
Оператор With в Python
Python – язык программирования, наделенный достаточно большим количеством инструментов, функций и встроенных стандартных команд. С их помощью удается создавать совершенно разное программное обеспечение. Огромную роль в таких приложениях может сыграть менеджер контекста.
Базовые определения
Перед тем как начинать изучение любого инструмента языка программирования, нужно запомнить несколько базовых определений:
- Переменная – единица хранения информации. Является именованной областью памяти.
- Функция – блок кода, который выполняет определенные операции для получения задуманного результата.
- Оператор – объект, который умеет при помощи специальных команд манипулировать операндами.
- Операнд – объекты кода, которыми умеет управлять оператор.
- Алгоритм – набор инструкций и указаний, необходимых для решения поставленной изначально задачи.
В Питоне присутствует множество операторов. Один из них используется для формирования менеджера контекста. Далее речь зайдет о With в Python.
Предназначение With и менеджера
With появился в Питоне с версии 2.5. Данный оператор является достаточно полезным. Данная функция используется почти каждым написанным на языке приложением.
With выполняет различные действия при активации:
- обрабатывает открытие/закрытие ресурсов;
- закрывает автоматически часть приложения, с которой больше не нужно работать.
Все это необходимо для того, чтобы грамотно распределять ресурсы устройства, а также оптимизировать функционирование имеющейся памяти.
Менеджер в Питоне
The Python – простой и функциональный язык разработки приложений. В нем можно обрабатывать файлы. В C и некоторых других ЯП для этого требуется вручную открывать и закрывать документ. Ниже – фрагмент кода, который помогает это сделать:
With делает это автоматически. Каждый раз открывать и закрывать файлы самостоятельно при формировании приложения на The Python не придется. У оператора with имеется контекст (блок), в котором он будет действовать.
Когда приложение выходит из соответствующего контекста, with будет автоматически закрывать ранее используемый файл. Все это приводит к тому, что рассматриваемый элемент в the Python носит название «диспетчер контекста».
with open(‘input.txt’, ‘r’) as file_obj:
Выше – пример того, как данная «опция» используется при обработке файлов. The operator будет всегда в конце закрывать документ. Это относится даже к ситуациям, когда само приложение работает/завершается некорректно.
Применение
Возможность использования with in the statement реализована в большом количестве классов Питона. Данный элемент the code:
- Сохраняет ссылку на объект в объекте контекста. Так называют объект, содержащий дополнительную информацию о своем состоянии (область видимости, модуль и так далее).
- После создания объекта the operator вызывает метод под названием __enter__ dunder для соответствующего компонента. С его помощью происходит открытие ресурсов для объекта. Может использоваться при реализации сохранения состояния элемента.
При работе с with необходимо обратить внимание на ключевое слово as in the statement. Оно фактически возвращает объект контекста. As a используется для того, чтобы получить элемент, возвращаемый при помощи функции open().
As in statements можно не использовать, если у разработчика имеется ссылка на исходный объект контекста в другом месте.
Далее предстоит перейти во вложенный блок операторов. Когда он закончится или встретится исключение, приложение выполнит для объекта the context __exit__. Она выступает в качестве первой функции безопасности. Используется всегда для того, чтобы высвободить ресурсы устройства и выйти из заданного контекста.
Собственные менеджеры – как создать
Для более удобного и быстрого изучения in The Python оператора with as необходимо научиться создавать собственные context managers для заданного класса.
Выше – пример того, как происходит формирование упомянутого элемента в The Python. Здесь есть метод __init__. Он написан для обработчика, устанавливает начальное состояние объектов и соответствующих переменных.
Также есть метод __enter__, сохраняющий состояние объекта и открывающий его. Это позволяет попасть внутрь заданного блока. После выполнения соответствующего фрагмента кода диспетчер выполнит __exit__, чтобы восстановить прежнее состояние the object. Файл будет закрыт.
На экране появится такая надпись вследствие выполнения приложения.
Методы
При работе с Python with a statement, необходимо помнить о методах для менеджеров контекста. Их всего два:
- __enter__. Используется для того, чтобы войти в context времени выполнения. Либо возвращает текущий объект, либо другой связанный элемент. Возвращаемое значение будет привязано к идентификатору в виде предположения with.
- Метод __exit__. Применяется для возврата результата логического характера. Указывает на любое произошедшее исключение. Если есть одно исключение для with, оно будет переведено в конец блока.
Выше – наглядный пример того, как реализованы эти методы in The Python with statement. А вот результат, который будет выведен на экран.
Здесь можно увидеть наглядный пример использования with…as, but лучше всего закончить дистанционные компьютерные курсы по The Python, чтобы быстро освоить язык и всего его возможности.
Менеджер контекста with в Python
Наверняка каждый, кто использует в своей работе python, сталкивался с ключевым словом with. Классическим примером будет являться работа с файлами:
Преимущество использования ключевого слова with перед вызовом функции open() в том, что функция file.close() вызовется автоматически и освободит занятые ресурсы после того, как отработает код. С первого взгляда кажется, что экономится лишь лишняя строка кода в нашей программе, но это не совсем так, главной особенностью конструкции with является то, что финальный код (в данном случае file.close()) вызывается гарантированно, даже в том случае, если при обработке интерпретатором строк внутри конструкции произойдет ошибка.
Важно заметить, что python позволяет разработчикам писать собственные контекстные менеджеры, и оборачивать в эту конструкцию практически любой объект, будь то функция или же целый класс с множеством методов. Этим свойством необходимо уметь пользоваться, если в вашей программе есть участки, выполнение которых критично для стабильной работы.
Для примера возьмем следующую задачу – наша программа должна запускать стороннее консольное приложение при помощи модуля subprocess, выполнять измерение времени выполнения этого приложения, а выполнение метода subprocess.terminate() мы сделаем автоматическим и гарантированным.
Код описывающий менеджер контекста:
class ForWith: def __init__(self, a): self.start_time = time.time() self.a = a self.b = subprocess.Popen(self.a) def __enter__(self): return self.b def __exit__(self, *args): print(time.time()-self.start_time) self.b.terminate()
Код для запуска нашего приложения:
program = [path_exe, path_data] with ForWith(program) as p: code = p.wait() print(‘exit code =’, code)
Рассмотрим приведенный выше код:
В созданном нами классе «ForWith» описано три магических метода:
__init__ — служит для описания атрибутов нашего класса, атрибут self.a передает аргументы вызова, self.b – это экземпляр класса subprocess.Popen
__enter__ — в этой функции описываются методы, вызываемые при старте контекстного менеджера. Объект, возвращаемый данной функцией, присваивается переменной в конце выражения with ForWith(*args) as p:. В нашем примере переменной p присвоится атрибут b, который в свою очередь – экземпляр класса subprocess.Popen. Теперь в нашем блоке кода, заключенном в менеджер контекста, мы можем вызывать методы класса subprocess.Popen обращаясь к переменной p, например: p.wait() или p.communicate().
__exit__ — магический метод, который будет вызван в завершении конструкции with, или в случае возникновения ошибки после нее. В этот метод передаются параметры завершения процесса, а код этого метода будет выполнен гарантированно. В нашем примере метод __exit__ выводит на экран время выполнения нашего приложения и вызывает функцию subprocess.terminate(), закрывающую наше приложение и освобождающую ресурсы.
Использование этого инструмента может помочь в ряде задач: например, мы можем закрывать соединение с сервером базы данных после выполнения запросов, можем в критический момент записать какой-либо логфайл или выполнить сохранение датафрейма на диск.