Можно ли обеспечить закрытие файлов и сессий без контекстного менеджера в Python
Что проверяют
Понимаете ли вы, что with не магия, а сахар над try/finally, и знаете ли, что происходит с ресурсом, который никто явно не закрыл. Следом обычно спрашивают, закроется ли файл сам, когда объект заберёт сборщик мусора.
Как отвечать
Да, можно. Инструменты по убыванию надёжности:
try/finally: прямой эквивалентwith.open()вызывается доtry, в блоке идёт работа, вfinallyвызываетсяclose(). Так же закрываютсяrequests.Session, соединение с базой или сессия SQLAlchemy;weakref.finalize: регистрирует функцию очистки, которая сработает при сборке объекта или при выходе из интерпретатора. Надёжнее__del__, но момент вызова всё равно не контролируется;atexit.register: закрытие глобальных ресурсов при штатном завершении процесса. Приkill -9илиos._exit()не сработает;__del__и подсчёт ссылок: в CPython файл закроется, когда исчезнет последняя ссылка, но это деталь реализации. В PyPy сборка отложена, а объект в циклической ссылке доживёт до прохода сборщика.
Хорошо показать, что with разворачивается в вызовы __enter__ и __exit__ внутри try/finally, поэтому любое ручное решение сводится к той же схеме. Незакрытый файл даёт ResourceWarning, который по умолчанию не показывается; увидеть его помогает запуск с -X dev или -W default.
Отдельно про асинхронный код: aiohttp.ClientSession закрывается через await session.close(), а забытое закрытие видно по предупреждению Unclosed client session.
Где спотыкаются
Ставят open() внутрь try. Если открытие упало, в finally переменной нет, и вместо исходной ошибки вылетает NameError.
Надеются, что сборщик мусора всё закроет. Для файлов в CPython это иногда так, для сокетов, пулов соединений и блокировок нет, и под нагрузкой процесс упирается в Too many open files.
Думают, что любой контекстный менеджер закрывает ресурс. with sqlite3.connect(...) управляет транзакцией, а соединение оставляет открытым, close() нужен отдельно.