Почему так важно закрывать файлы в Python?
Оглавление
- Короче говоря, файлы - это ресурсы, ограниченные операционной системой
- Что Происходит, Когда Вы Открываете Слишком Много Файлов?
- Каковы реальные последствия превышения лимита файлов?
- Почему операционная система ограничивает дескрипторы файлов?
- Что произойдет, если вы не закроете файл и Python выйдет из строя?
- Заключение
В какой-то момент вашего пути по написанию кода на Python вы узнаете, что для открытия файлов следует использовать контекстный менеджер. Контекстные менеджеры Python позволяют легко закрыть ваши файлы как только вы закончите с ними:
with open("hello.txt", mode="w") as file:
file.write("Hello, World!")
Инструкция with запускает контекстный менеджер. В этом примере контекстный менеджер открывает файл hello.txt и управляет файловым ресурсом до тех пор, пока не будет запущен.>контекст активен. В общем, весь код в блоке с отступом зависит от того, открыт ли файловый объект. Как только блок с отступом завершается или возникает исключение, файл закрывается.
Если вы не используете контекстный менеджер или работаете на другом языке, вы можете явно закрыть файлы следующим образом try ... finally :
try:
file = open("hello.txt", mode="w")
file.write("Hello, World!")
finally:
file.close()
Блок finally, который закрывает файл, выполняется безоговорочно, независимо от того, успешно ли выполняется блок try. Хотя этот синтаксис эффективно закрывает файл, контекстный менеджер Python предлагает менее подробный и более интуитивно понятный синтаксис. Кроме того, это немного более гибко, чем просто обертывание вашего кода с помощью try ... finally.
Вы, вероятно, уже используете контекстные менеджеры для управления файлами, но задумывались ли вы когда-нибудь, почему большинство руководств и четыре из пяти стоматологов рекомендуют это делать? Короче говоря, почему важно закрывать файлы в Python?
В этом руководстве вы подробно ответите на этот вопрос. Во-первых, вы узнаете о том, что файловые дескрипторы являются ограниченным ресурсом. Затем вы поэкспериментируете с последствиями того, что вы не закрываете свои файлы.
Скачайте бесплатно: Ознакомьтесь с примером главы из CPython Internals: Ваше руководство по интерпретатору Python 3, в которой показано, как разблокировать внутреннюю ознакомьтесь с работой языка Python, скомпилируйте интерпретатор Python из исходного кода и примите участие в разработке CPython.
Короче говоря, файлы - это ресурсы, ограниченные операционной системой
питон делегатов файловых операций на Операционная система. Операционная система является посредником между процессами,, такими как Python, и всеми системными ресурсами, такими как жесткий диск, оперативная память и процессорное время.
Когда вы открываете файл с помощью open(), вы выполняете системный вызов операционной системы, чтобы найти этот файл на жестком диске и подготовить его к чтению или записи. Затем операционная система вернет целое число без знака, называемое файловым дескриптором в Windows и файловым дескриптором в UNIX-подобных системах, включая Linux и macOS:
Процесс на Python, выполняющий системный вызов и получающий целое число 10 в качестве дескриптора файла
Как только у вас будет номер, связанный с файлом, вы будете готовы к выполнению операций чтения или записи. Всякий раз, когда Python хочет прочитать, записать или закрыть файл, он выполняет другой системный вызов, предоставляя номер дескриптора файла. У объекта Python file есть .fileno() метод, который вы можете использовать для поиска дескриптора файла:
>>> with open("test_file.txt", mode="w") as file:
... file.fileno()
...
4
Метод .fileno() для открытого файлового объекта вернет целое число, используемое операционной системой в качестве файлового дескриптора. Точно так же, как вы могли бы использовать поле ID для получения записи из базы данных, Python предоставляет этот номер операционной системе каждый раз, когда она считывает или записывает данные из файла.
Операционные системы ограничивают количество открытых файлов, которые может иметь любой отдельный процесс. Обычно это число исчисляется тысячами. Операционные системы устанавливают это ограничение, потому что, если процесс пытается открыть тысячи файловых дескрипторов, с процессом, вероятно, что-то не так. Несмотря на то, что тысячи файлов могут показаться большим количеством, ограничение все равно может быть превышено.
Помимо риска превышения лимита, сохранение файлов открытыми делает вас уязвимым к потере данных. В целом, Python и операционная система прилагают все усилия, чтобы защитить вас от потери данных. Но если ваша программа или компьютер выйдет из строя, обычные процедуры могут не выполняться, а открытые файлы могут быть повреждены.
Примечание: В некоторых библиотеках есть специальные методы и функции, которые, по-видимому, открывают файлы без контекстного менеджера. Например, в библиотеке pathlib есть .write_text(),, а в pandas есть read_csv().
Однако они должным образом управляют ресурсами под капотом, так что вам не нужно использовать контекстный менеджер в таких случаях. Лучше всего обратиться к документации к используемой вами библиотеке, чтобы узнать, нужен ли вам контекстный менеджер или нет.
Короче говоря, предоставление контекстным менеджерам возможности управлять вашими файлами — это защитный прием, который легко применять на практике и который улучшает ваш код, так что вы вполне можете это сделать. Это как пристегнуться ремнем безопасности. Вероятно, вам это не понадобится, но затраты на то, чтобы обойтись без этого, могут быть высокими.
В оставшейся части этого руководства вы более подробно ознакомитесь с ограничениями, последствиями и опасностями, связанными с незакрытием файлов. В следующем разделе вы узнаете об ошибке Too many open files.
Что происходит, Когда Вы Открываете Слишком много Файлов?
В этом разделе вы узнаете, что происходит, когда вы сталкиваетесь с ограничением по файлам. Вы сможете сделать это, опробовав фрагмент кода, который создаст множество открытых файлов и вызовет OSError.
Примечание: Как следует из OS в OSError, ограничение устанавливается операционной системой, а не Python. Однако теоретически операционная система может обрабатывать гораздо больше файловых дескрипторов. Позже вы узнаете больше о том, почему операционная система ограничивает дескрипторы файлов.
Вы можете проверить ограничение на количество файлов для каждого процесса в вашей операционной системе, попробовав открыть тысячи файлов одновременно. Вы сохраните файловые объекты в виде списка, чтобы они не очищались автоматически. Но сначала вам нужно немного заняться домашним хозяйством, чтобы убедиться, что вы не создаете много ненужных файлов там, где они вам не нужны:
$ mkdir file_experiment
$ cd file_experiment
Достаточно создать папку, в которую вы можете поместить файлы, а затем перейти к этой папке. Затем вы можете открыть Python REPL и попытаться создать тысячи файлов:
>>> files = [open(f"file-{n}.txt", mode="w") for n in range(10_000)]
Traceback (most recent call last):
...
OSError: [Errno 24] Too many open files: 'file-1021.txt'
В этом фрагменте делается попытка открыть десять тысяч файлов и сохранить их в списке. Операционная система начинает создавать файлы, но прекращает работу, как только достигает своего предела. Если вы перечислите файлы в вашем недавно созданном каталоге, вы заметите, что, несмотря на то, что понимание списка в конечном итоге не удалось, операционная система сохранила многие файлы — просто не те десять тысяч, которые вы просили.
Ограничение, с которым вы сталкиваетесь, варьируется в зависимости от операционной системы и по умолчанию кажется больше в Windows. В зависимости от операционной системы существуют способы увеличить это ограничение на количество файлов на процесс. Однако вам следует спросить себя, действительно ли вам это нужно. Существует всего несколько обоснованных вариантов использования этого решения.
Один из допустимых сценариев - для серверов. Серверы работают с сокетами, которые обрабатываются так же, как файлы. Операционная система отслеживает сокеты в таблице файлов, используя дескрипторы файлов. Серверу может потребоваться открыть много сокетов для каждого клиента, к которому он подключается. Кроме того, сервер может взаимодействовать с несколькими клиентами. В этой ситуации могут потребоваться многие тысячи дескрипторов файлов.
Как ни странно, несмотря на то, что некоторые приложения могут требовать повышения лимита операционной системы на открытие файлов, обычно именно эти приложения должны особенно тщательно закрывать файлы!
Возможно, вы считаете, что вам не грозит непосредственная опасность превысить лимит. Тем не менее, читайте дальше, потому что в следующем разделе вы более подробно рассмотрите некоторые последствия случайного превышения этого лимита.
Каковы реальные последствия превышения лимита файлов?
Если вы открываете файлы и никогда не закрываете их в Python, вы можете не заметить никакой разницы, особенно если работаете над однофайловыми сценариями или небольшими проектами. Однако по мере усложнения проектов, над которыми вы работаете, вы будете все чаще сталкиваться с проблемными ситуациями.
Представьте, что вы работаете в большой команде над массивной кодовой базой. И вот однажды вы достигаете предела по количеству открытых файлов. Проблема в том, что сообщение об ошибке для ограничения не сообщит вам , в чем проблема. Это будет обычный OSError который вы видели ранее, который говорит вам только о Too many open files.
В вашей кодовой базе могут быть тысячи мест, где вы открываете файлы. Представьте, что вы ищете места, где код неправильно обрабатывает файлы. Представьте, что код передает файловые объекты между функциями, и вы не можете сразу определить, будет ли какой-либо данный файловый объект в конечном итоге закрыт или нет. Это не самое веселое времяпрепровождение.
Если вам интересно, есть способы изучить дескрипторы открытых файлов в вашей системе. Разверните следующий блок для изучения:
Установить process hacker: Откройте приложение и нажмите кнопку Найти дескрипторы или библиотеки DLL. Установите флажок регулярное выражение и введите .*, чтобы просмотреть все дескрипторы файлов с сопутствующей информацией.
Официальная версия process hacker от Microsoft является частью утилит Sysinternals, а именно Process Monitor и .Обозреватель процессов.
Возможно, вам потребуется установить lsof, это утилита для Linux для list oручка fилы. С помощью этой утилиты вы можете получить информацию и подсчитать, сколько всего открыто файлов:
$ lsof | head
$ lsof | wc -l
Команда lsof выводит новую строку для каждого открытого файла с основной информацией об этом файле. При вводе ее в команду head вы увидите начало вывода, включая имена столбцов.
Результат lsof может быть передан в команду wc, или подсчет слов. Переключатель -l означает, что будут учитываться только новые строки. Это число, вероятно, будет исчисляться сотнями тысяч.
Вы можете преобразовать выходные данные lsof в grep, чтобы найти строки, содержащие строку, подобную python. Вы также можете ввести идентификатор процесса, который может быть полезен, если вы хотите найти файловые дескрипторы:
$ lsof | grep python
Эта команда отфильтрует все строки, которые не содержат термин после grep, в данном случае python.
Если вам интересно узнать о теоретическом ограничении для файлов в вашей системе, вы можете изучить это в системах на базе UNIX, изучив содержимое специального файла:
$ cat /proc/sys/fs/file-max
Это число сильно зависит от платформы, но, скорее всего, оно огромно. В системе почти наверняка закончатся другие ресурсы, прежде чем будет достигнут этот предел.
Вы можете задаться вопросом, почему операционная система ограничивает доступ к файлам. Предположительно, она может обрабатывать гораздо больше дескрипторов файлов, чем показывает, верно? В следующем разделе вы узнаете, почему операционная система заботится об этом.
Почему операционная система ограничивает обработку файлов?
Фактические пределы количества файлов, которые операционная система может поддерживать открытыми одновременно, огромны. Речь идет о миллионах файлов. Но на самом деле достижение этого предела и присвоение ему фиксированного значения не является однозначным решением. Как правило, в системе заканчиваются другие ресурсы до того, как закончатся дескрипторы файлов.
Ограничение является консервативным с точки зрения операционной системы, но достаточным с точки зрения большинства программ. С точки зрения операционной системы, любой процесс, который достигает этого ограничения, вероятно, приводит к утечке дескрипторов файлов вместе с другими ресурсами.
Утечка ресурсов может быть вызвана неправильным программированием или попыткой вредоносной программы атаковать систему. Вот почему операционная система устанавливает ограничения — чтобы обезопасить вас от других и от вас самих!
Кроме того, для большинства приложений не имеет смысла открывать так много файлов. На одном жестком диске одновременно может выполняться не более одной операции чтения или записи, поэтому это не ускоряет работу, если вы работаете только с файлами.
Итак, вы знаете, что открывать много файлов проблематично, но есть и другие недостатки в том, чтобы не закрывать файлы в Python, даже если вы открываете всего несколько файлов.
Что произойдет, если вы не закроете файл и произойдет сбой Python?
В этом разделе вы поэкспериментируете с имитацией сбоя и увидите, как это влияет на открытые файлы. Вы можете использовать специальную функцию в модуле os, которая завершит работу без выполнения какой-либо очистки, которую обычно выполняет Python, но сначала вы увидите, как все обычно очищается.
Выполнение операций записи для каждой команды может быть дорогостоящим. По этой причине в Python по умолчанию используется буфер , который собирает операции записи. Когда буфер заполняется или когда файл закрывается явно, буфер очищается, и операция записи завершается.
Python прилагает все усилия, чтобы убирать за собой. В большинстве случаев он сам проактивно очищает и закрывает файлы:
# write_hello.py
file = open("hello.txt", mode="w")
file.write("Hello, world!")
При выполнении этого кода операционная система создает файл. Операционная система также записывает содержимое, даже если вы никогда не удаляете и не закрываете файл в коде. Эта очистка и закрытие выполняются с помощью процедуры очистки, которую Python выполнит в конце выполнения.
Однако иногда выходы не так хорошо контролируются, и сбой может закончиться без этой очистки:
# crash_hello.py
import os
file = open("crash.txt", mode="w")
file.write("Hello, world!")
os._exit(1)
После запуска приведенного выше фрагмента вы можете использовать cat для проверки содержимого только что созданного файла:
$ cat crash.txt
$ # No output!
Вы увидите, что, несмотря на то, что операционная система создала файл, в нем нет никакого содержимого. Отсутствие вывода связано с тем, что os._exit() обходит обычную процедуру выхода из Python, имитируя сбой. Тем не менее, даже этот тип моделирования относительно контролируем, поскольку он предполагает, что сбой произошел на Python, а не в вашей операционной системе.
Как только Python будет завершен, операционная система также выполнит свою собственную очистку, закрыв все файловые дескрипторы, открытые процессом. Сбои могут происходить на многих уровнях и мешать очистке операционной системы, в результате чего дескрипторы файлов могут зависать.
В Windows, например, зависание дескрипторов файлов может создавать проблемы, поскольку любой процесс, открывающий файл, также блокирует его. Другой процесс не сможет открыть этот файл, пока он не будет закрыт. Пользователи Windows, возможно, знакомы с вредоносными процессами, которые не позволяют открывать или удалять файлы.
Что может быть хуже, чем быть заблокированным для доступа к файлам? Утечка дескрипторов файлов может представлять угрозу безопасности поскольку разрешения, связанные с файлами, иногда перепутываются.
Примечание: Наиболее распространенная реализация Python, CPython, идет дальше в очистке ваших зависающих дескрипторов файлов, чем вы могли бы подумать. Он использует подсчет ссылок для сбора мусора, так что файлы закрываются, как только на них больше нет ссылок. Тем не менее, другие реализации, такие как PyPy, используют другие стратегии, которые могут быть не столь агрессивными при очистке неиспользуемых дескрипторов файлов.
Тот факт, что некоторые реализации могут работать не так эффективно, как CPython, является еще одним аргументом в пользу постоянного использования контекстного менеджера!
Утечка файловых дескрипторов и потеря содержимого в буфере - это уже само по себе плохо, но сбой, прерывающий работу с файлом, также может привести к повреждению файла. Это значительно увеличивает вероятность потери данных. Опять же, это маловероятные сценарии, но они могут быть дорогостоящими.
Вы никогда не сможете полностью оградить себя от сбоев, но вы можете уменьшить риск их возникновения, используя контекстный менеджер. Синтаксис контекстного менеджера естественным образом приведет вас к написанию кода таким образом, чтобы файл оставался открытым только до тех пор, пока это необходимо.
Заключение
Вы узнали почему важно закрывать файлы в Python. Поскольку файлы представляют собой ограниченные ресурсы, управляемые операционной системой, закрытие файлов после их использования защитит от проблем, которые трудно поддаются отладке, таких как нехватка дескрипторов файлов или повреждение данных. Лучшей защитой всегда является открытие файлов с помощью контекстного менеджера.
Если копнуть глубже, то вы уже видели, что происходит, когда вы открываете слишком много файлов и провоцируете сбой, который приводит к потере содержимого файла. Дополнительные сведения об открытии файлов см. в разделе Чтение и запись файлов в Python. Подробное руководство по контекстным менеджерам см. в Контекстных менеджерах и в with инструкции Python..