Время прочтения: 3 мин.

Наверняка каждый, кто использует в своей работе python, сталкивался с ключевым словом with. Классическим примером будет являться работа с файлами:

with open(path, 'w') as file:
   file.write(data)

Преимущество использования ключевого слова 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(), закрывающую наше приложение и освобождающую ресурсы.

Использование этого инструмента может помочь в ряде задач: например, мы можем закрывать соединение с сервером базы данных после выполнения запросов, можем в критический момент записать какой-либо логфайл или выполнить сохранение датафрейма на диск.