Почему важно закрывать файлы в Python? Python

Почему важно закрывать файлы в Python?

Чем чреваты файлы, своевременно не закрытые в операционной системе

При создании программного обеспечения с функциями обработки файлов, у вас всегда есть риск получения ошибки операционной системы (ОС) типа 'Too many open files'слишком много открытых файлов. Это объясняется тем, что количество открытых файлов, как и любой другой ресурс для ОС всегда является ограниченным. Конечно, в теории современные компьютеры способны одновременно обрабатывать сотни тысяч, а то и миллионы открытых файлов. Но реально, следует учитывать, что с нарастанием функциональных возможностей и сервисов ОС, их контроль должен распределяться на все большое количество технических параметров и возможностей компьютера. Следовательно, удельный вес ресурсов, отведенных в ОС на работу с файлами, снижается. Так, на примере нижеприведенного фрагмента Python программы можно увидеть, что даже количество в несколько десятков тысяч одновременно открытых файлов, далеко не всегда приемлемо для современных ОС и компьютеров:

>>> files = [open(f"file-{n}.txt", mode="w") for n in range(10000)]
Traceback (most recent call last):
    ...
OSError: [Errno 24] Too many open files: 'file-1021.txt'

Данный фрагмент пытается открыть десять тысяч файлов и сохранить их на диске. Но, в итоге ОС выдает ошибку Too many open files, как только лимит одновременно открытых файлов в ней исчерпывается. Более того, реально созданное на диске количество файлов зачастую может и не совпадать с тем, что выдается сообщением об ошибке при сбои вышеприведенной программы.

Рекомендации: С целью предотвращения мешанины среди вновь создаваемых и уже имеющихся файлов на диске, вышеприведенную программу желательно запускать в предварительно созданной директории. Для UNIX-подобных ОС это реализуется следующими командами:

$ mkdir file_experiment
$ cd file_experiment

Превышение лимита одновременно открытых файлов довольно часто также сопровождается блокированием файлов, когда помимо того, что в них невозможно ничего записать, эти файлы еще и невозможно ни удалить, ни переместить. В этом случае вам скорее всего придется пересматривать тысячи, а то и десятки тысяч файлов на предмет того, не являются ли они пустыми и, можно ли их удалить.

Для борьбы с проблемами, вызванными несвоевременным закрытие файлов в Python предусмотрен ряд соответствующих механизмов. Но для того, чтобы более подробно остановится на них, нам предварительно понадобится рассмотреть процесс взаимодействия Python и ОС при работе с файлами.

Что такое "Файловые дескрипторы" и, для чего они используются

Базовая функция любой ОС состоит в обеспечении посредничества между прикладными программными процессами, такими как Python и, различными системными ресурсами вроде жестких дисков, оперативной памяти и процессорного времени.

Следовательно, открывая на Python определенный файл с помощью функции open(), вы на самом деле осуществляете системный вызов операционной системе, с тем чтобы найти этот файл на информационном носителе, а также подготовить его для чтения или записи. В итоге ОС возвращает в Python целое число без знака, называемое в Windows - file handle, а в UNIX-подобных системах типа Linux и macOS - file descriptor:

image

Рис. 1. Процесс Python, выполняющий системный вызов и получающий целое число 10 в качестве дескриптора файла.

Впоследствии, полученное вами целое число в виде номера файлового дескриптора должно использоваться программой Python всякий раз, когда ей нужно прочитать, записать или закрыть соответствующий файл. Это делается путем еще одного вызова ОС, где указывается номер соответствующего дескриптора, нужного для работы файла. Хотя непосредственное значение файлового дескриптора для конкретных файлов в виде целого числа редко необходимо в Python программах, его все же можно узнать, воспользовавшись методом файлового объекта - .fileno():

>>> with open("test_file.txt", mode="w") as file:
...     file.fileno()
...
4

Применение механизмов, обеспечивающих своевременное закрытие файлов

Как уже было сказано, в Python существует множество механизмов, предотвращающих ошибки из-за отставленных открытыми файлов и соответственно, обеспечивающих своевременное закрытие тех файлов, которые использовались программой. Все эти механизмы, как правило, обеспечивают автоматическое закрытие файлов при завершении всей программы, либо каких-то ее процессов за счет использования вышеописанных файловых дескрипторов. Так, например, при выполнении этого кода:

# write_hello.py
file = open("hello.txt", mode="w")
file.write("Hello, world!")

Python после создания файла hello.txt и записи в него текста 'Hello, world!' сам заботиться о том, чтобы этот файл был автоматически закрыт после благополучного завершения программы.

Однако, что, если данная программа не завершится корректно, например, из-за отключения электричества в момент ее выполнения? Смоделируем эту ситуацию программным путем за счет применения функции os._exit(1), имитирующей сбой программы:

# crash_hello.py
import os
file = open("crash.txt", mode="w")
file.write("Hello, world!")
os._exit(1)

Результат выполнения последнего фрагмента кода можно посмотреть путем вывода содержимого файла ' crash.txt', используя команду cat (для UNIX-систем):

$ cat crash.txt
$ # No output!

Как видим, что хотя вышеназванный файл и был создан ОС, но он оказался пустым. Таким образом, при написании программ лучше все же закрывать файлы сразу после того, как они были использованы. Это можно сделать путем использования конструкции try... finally:

try:
    file = open("hello.txt", mode="w")
    file.write("Hello, World!")
finally:
    file.close()

Где блок finally безальтернативно закрывает файл 'hello.txt' независимо от того, успешно ли был выполнен блок try. Вмести с тем, использования конструкции try... finally для своевременного закрытия файлов является довольно громоздким и трудночитаемым. Гораздо более компактный и интуитивно понятный подход в Python обеспечивается применением так называемого контекстного менеджера, благодаря которому вышеприведенный код выглядит следующим образом:

with open("hello.txt", mode="w") as file:
    file.write("Hello, World!")

Первой строкой в этом коде (оператором with) инициируется контекстный менеджер - блок, все последующие строки которого являются строками с отступом. Как только эти строки с отступом заканчиваются или же при их выполнении вызывается какое-либо исключение, файлы, обрабатываемые в данном контекстном менеджере, закрываются. Таким образом программы, написанные с использованием подобных менеджеров компактны, интуитивно понятны и читабельны.

Выводы

Поскольку файлы являются ограниченными ресурсами, управляемыми ОС, то при работе с ними всегда существует опасность возникновения трудно отлаживаемых программных сбоев, связанных либо с исчерпанием лимита ОС для файловых дескрипторов, либо просто с повреждением данных при их сохранении на диске. Поэтому во избежание таких ситуаций в Python предусмотрена масса механизмов, среди которых лучшим вариантом является применение соответствующего контекстного менеджера.

Практический Python для начинающих
Практический Python для начинающих

Станьте junior Python программистом за 7 месяцев

 7 месяцев

Возможно будет интересно

🏆 Hello, world! Python
Новичок
🏆 Hello, world!

Мы вчера запустили новый www.pylot.me. Должны были в следующую среду, но запустили вчера.

2022-10-04
Как практиковаться в Python? Python
Новичок
Как практиковаться в Python?

Для улучшения качества знаний и повышения уровня программиста, необходим постоянный практикум. Где можно это организовать самостоятельно, и как практиковаться в Python?

2022-10-19
Условные конструкции и сопоставление структурных шаблонов Шпаргалки
Новичок
Условные конструкции и сопоставление структурных шаблонов

Шпаргалка по условным конструкциям и сопоставлению структурных шаблонов

2022-11-09