Привет. В этом видео мы поговорим с вами о контекстных менеджерах. С контекстными менеджерами вы уже работаете и работали, когда открывали, например, файлы. Мы с вами конкретно не останавливались на том, как это происходит внутри, но вы знаете, что если использовать контекстный менеджер with с открытием файла, вам не нужно заботиться о том, чтобы его потом закрыть, то есть контекстный менеджер делает это за вас. Вы открываете файл, записываете открытый файл в переменную f, записываете какие-то данные, и потом он сам как-то закрывается, вам не нужно писать f close. Контекстные менеджеры позволяют вам делать именно это. Они позволяют определить поведение, которое происходит в начале и в конце блока исполнения, блока with. Часто бывает необходимо, как, например, в случае с файлами, отрывать и закрывать какой-то ресурс в обязательном порядке. Например, вам нужно после открытия сокета его закрыть или закрыть обязательно какое-то соединение. Чтобы об этом не заботиться, об этом не помнить, можно использовать контекстный менеджер. Также, например, они используются при работе с транзакциями. Вам нужно обязательно либо закончить транзакцию, либо ее откатить, и вы можете определить контекстный менеджер, который будет вам управлять поведением открытия и закрытия блока кода. Чтобы определить свой контекстный менеджер, как вы могли догадаться, нужно написать свой класс с магическими методами. Эти магические методы enter и exit, которые как раз говорят о том, что происходит в начале и в конце контекстного менеджера. Давайте попробуем написать аналог контекстного менеджера стандартного для открытия файлов, назовем его open file. Обратите внимание, название класса с маленькой буквы, потому что это контекстный менеджер, это не CamelCase. Итак, у нас контекстный менеджер используется точно так же, как и стандартный, мы вызываем open file, в этот момент создается объект, то есть вызывается метод init, и мы записываем в переменную класса f, открытый файл с каким-то именем и открытый с каким-то mode'ом. Отлично. Потом эта переменная f записывается из метода enter. То есть из метода enter у нас возвращается что-то, если нам нужно это потом записать с помощью оператора as. Мы можем ничего не возвращать из enter, и тогда у нас нет смысла использовать as. Что логично в методе exit у нас определяется поведение, которое происходит при выходе из блока контекстного менеджера. В данном случае нам нужно закрыть обязательно файл. То есть когда у нас закончится этот блок, то есть где-то здесь, у нас произойдет закрытие файла, и вам не нужно об этом заботиться каждый раз, когда вы используете контекстный менеджер. Итак, мы открыли файл и записали в него какую-то строчку. Если попробовать прочитать этот файл, окажется, что она действительно там, файл у нас открылся и закрылся сам. Это очень удобно, и вы можете определять свое собственное поведение при выходе из блока. Еще одна важная особенность контекстных менеджеров - они позволяют вам управлять исключениями, которые произошли внутри блока. Мы можем эти исключения обрабатывать и определять какое-то поведение. Например, мы можем определить контекстный менеджер suppress exception, который будет работать с exception'ами, которые произошли внутри. Обратите внимание, в данном случае мы не используем as, оператор as, поэтому нам не важно, что возвращается из enter'а, просто его определили и написали return. Могли написать просто pass. Итак, у нас есть контекстный менеджер suppress exception, который принимает exception, то есть exception type, класс exception'а. Мы этот exception type записываем и потом будем проверять, произошло ли действительно это исключение или какое-то другое. Поведение таково, что если исключение произошло от того типа, который нам интересен, мы делаем вид, что ничего не произошло. То есть мы подавляем это исключение в suppress mode. Мы просто выводим, что ничего не произошло, и возвращаем true. Нужно обязательно вернуть true из exit'а при исключении, чтобы воспроизведение кода продолжилось и exception не был выброшен. Таким образом, мы можем поделить на 0 и exception засаппрессится, ничего не произойдет. Часто бывает это очень полезно и удобно делать. Что интересно, такой контекстный менеджер уже есть в стандартной библиотеке в contextlib'е, и вы можете его использовать и можете посмотреть, как он на самом деле работает внутри и окажется, что он работает примерно так же. Давайте попробуем написать свой собственный контекстный менеджер в качестве примера. Это будет контекстный менеджер, который считает время, проведенное внутри него. То есть у нас при открытии блока что-то произошло, и при закрытии мы должны вывести время, которое произошло внутри контекстного менеджера. Давайте напишем наш класс, назовем его timer и определим сразу методы enter и exit, потому что именно они говорят нам о том, что это контекстный менеджер. Отлично. Как мы будем использовать наш класс? Мы будем использовать оператор with timer. Потом что-то должно случиться внутри контекстного менеджера, и мы должны вывести время, в которое это все дело происходило. Итак, чтобы нам считать время, которое прошло внутри контекстного менеджера, нам нужно где-то завести переменную, которая берет начало, собственно, которая и записывает время в начале выполнения операции. Происходит это, конечно, в методе init, потому что у нас создается объект класса. И давайте с помощью встроенного модуля time сохраним в переменную start текущее время. То есть в момент инициализации, то есть вот здесь вот, когда у нас вызвался таймер, у нас запишется текущее время. В enter'е мы просто вернем ничего, и в exit'е нам интересно вывести время, которое прошло с момента начала. Для этого мы просто напишем time, time и на self.start. То есть выведем время, которое как раз прошло. И чтобы у нас контекстный менеджер разрешился не мгновенно, давайте напишем здесь time.sleep и будем спать в течение одной секунды. Отлично. Да, у нас действительно вывелось время, давайте сделаем это немного посимпатичнее, как-то так, да. Давайте попробуем модифицировать немного контекстный менеджер, чтобы что-то возвращать из return'а, чтобы можно было, например, нам смотреть, сколько времени прошло на текущий момент, если у нас несколько, допустим, операций sleep. Здесь мы хотим, например, as t и вывести current_time и сделать еще один time.sleep. Что нам для этого нужно сделать? Например, мы можем вернуть самого себя в enter'е и определить метод current_time, который будет возвращать время, прошедшее с начала выполнения. Делает он точно то же самое, time.time − self.start. Ну и здесь можно тоже заменить тогда на self.current_time. Давайте отформатируем строчку, чтобы было понятно, что именно выводится у нас. Итак. Запускаем наш контекстный менеджер и забываем вызвать current_time. Давайте попробуем еще раз. Итак, у нас вначале выводится время текущее, то есть прошла одна секунда, один sleep, и итоговое время две секунды с небольшим. Собственно, как мы и ожидали. Мы написали с вами контекстный менеджер, который считает время, проведенное внутри него и плюс еще позволяет вам вывести время, которое прошло с момента начала внутри самого контекстного менеджера. Итак, контекстные менеджеры позволяют вам определить поведение при входе и выходе из блока кода with, что часто бывает полезно, например, при закрытии каких-то ресурсов или, например, при транзакционной какой-то деятельности. На этом все, увидимся в следующем видео.