Что такое метод __exit__ в контекстном менеджере Python и как он работает с файлами?
Что проверяют
Понимаете ли вы, что with это не сахар вокруг close, а протокол с гарантиями. Проверяют знание сигнатуры, правил подавления исключений и того, почему освобождение ресурса нельзя оставлять сборщику мусора.
Как отвечать
Контекстный менеджер это объект с двумя методами. __enter__ готовит ресурс и возвращает то, что попадёт в переменную после as. __exit__ вызывается при выходе из блока в любом случае: нормальное завершение, исключение, return или break внутри блока.
class Opened:
def __enter__(self):
self.f = open(self.path)
return self.f
def __exit__(self, exc_type, exc, tb):
self.f.close()
return FalseГлавное про аргументы и возврат: при нормальном выходе все три параметра равны None, при исключении в них приходят тип, объект исключения и трассировка. Истинное значение из __exit__ гасит исключение и выполнение продолжается после блока, False или None пропускают его дальше.
Для файлов это значит, что дескриптор закроется даже при ошибке внутри блока, а буфер будет сброшен при закрытии. Сам файловый объект уже контекстный менеджер, писать обёртку ради close не нужно. Короткий вариант того же даёт декоратор contextmanager из contextlib: код до yield это вход, блок finally это выход.
Где спотыкаются
Думают, что __exit__ обязан вернуть True, раз он про обработку исключений. На деле это редкий осознанный случай вроде подавления одного конкретного типа, а привычка возвращать True превращает менеджер в глушитель ошибок.
Считают, что метод отработает всегда. Он не отработает при жёстком завершении процесса и не будет вызван, если исключение случилось внутри __enter__: раз вход не состоялся, парного выхода нет.
Захватывают ресурс не там: открывают файл в конструкторе вместо __enter__. Тогда созданный, но не использованный в with объект держит дескриптор, а один и тот же менеджер нельзя переиспользовать во вложенных блоках.