Как закрыть объект файла, полученный после open в Python?
Что проверяют
Знаете ли вы, зачем файл вообще закрывать, и пишете ли код, который закрывает его и при исключении. Следом спрашивают, как устроен with и что будет, если файл не закрыть.
Как отвечать
Базовый ответ: f.close(), а на практике with:
with open("data.txt", encoding="utf-8") as f:
data = f.read()
# здесь f.closed == Truewith вызывает у файла __enter__ на входе и __exit__ на выходе, причём __exit__ срабатывает и при исключении. Ручной эквивалент: f = open(...), затем try: ... finally: f.close(). Сам open ставят до try: если он упадёт, закрывать нечего.
Зачем закрывать:
- буфер: запись идёт в буфер, и в файл данные гарантированно уходят только при
flush()илиclose(). Чтобы они физически оказались на диске, послеflush()нужен ещёos.fsync(); - дескрипторы: у процесса их ограниченное число, в Linux мягкий лимит часто 1024 (
ulimit -n). Сервис, который открывает файлы в цикле и не закрывает, однажды получитOSError: [Errno 24] Too many open files; - блокировки: в Windows открытый файл нельзя удалить или переименовать.
Полезные детали: close() можно вызывать повторно, ошибки не будет. После закрытия чтение даёт ValueError: I/O operation on closed file. Несколько файлов открываются в одном with через запятую, а для заранее неизвестного числа файлов есть contextlib.ExitStack. Для разового чтения удобен Path("data.txt").read_text(), он закрывает файл сам.
Где спотыкаются
Надеются на сборщик мусора. В CPython файл закроется, когда пропадёт последняя ссылка, но в PyPy это может случиться намного позже. Незакрытый файл CPython отмечает ResourceWarning, его видно в режиме python -X dev.
Возвращают файловый объект из функции изнутри with. Блок закрывает файл на выходе, и вызывающий код получает уже закрытый объект. Обратный случай: генератор с with внутри держит файл открытым, пока его не дочитают или не закроют.
Называют проблему «утечкой памяти». Утекают дескрипторы и несброшенные данные, и говорить лучше именно об этом.