ПрограммированиеPython Backend разработчик

Как работает контекст-менеджер в Python, зачем он нужен и как реализовать свой собственный через протоколы __enter__ и __exit__? Какие тонкости важно учесть?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

Контекст-менеджер — это объект, который определяет поведение входа и выхода из блока with, обеспечивая автоматическое управление ресурсами (файлами, соединениями и др.). Реализуется с помощью методов __enter__ и __exit__ в классе или через декоратор @contextmanager из модуля contextlib.

Пример реализации:

class FileManager: def __init__(self, filename, mode): self.filename = filename self.mode = mode self.file = None def __enter__(self): self.file = open(self.filename, self.mode) return self.file def __exit__(self, exc_type, exc_val, exc_tb): if self.file: self.file.close() with FileManager('test.txt', 'w') as f: f.write('Hello')

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

Вопрос с подвохом.

Вопрос: Если в блоке with возникает исключение, вызывает ли Python метод __exit__? Как передаются параметры ошибки?

Ответ: Да, метод __exit__ всегда вызывается, даже если происходит исключение. При этом в него передаются тип исключения, его значение и traceback. Если __exit__ возвращает True, исключение подавляется.

class Simple: def __enter__(self): print('Enter') def __exit__(self, exc_type, exc_val, exc_tb): print('Exit') print(exc_type, exc_val) return True # ошибка не "вылетает" наружу with Simple(): raise ValueError('boom!')

Примеры реальных ошибок из-за незнания тонкостей темы.


История

Забыли реализовать __exit__ в самописном менеджере работы с файловыми дескрипторами — в случае исключения файл не закрывался, что приводило к утечке файловых дескрипторов и отказам при работе с большим количеством файлов.

История

Использовали сторонний контекст-менеджер для управления базой данных, который возвращал True в __exit__ для всех исключений. Это "глушило" ошибки и приводило к незаметным сбоям и нарушениям целостности данных, так как логика считала, что транзакция успешна.

История

Воспользовались декоратором @contextmanager из модуля contextlib, но забыли обработать исключения внутри yield, из-за чего соединение с сокетом оставалось не закрытым при падении кода и сервер "завис" с открытыми портами.