Python: безопасное создание вложенного каталога
Управление файлами - один из самых важных навыков, которым нужно овладеть на любом языке программирования, и правильное выполнение этого имеет первостепенное значение. Ошибка может вызвать проблемы в вашей программе, других программах, работающих в той же системе, и даже в самой системе.
Возможные ошибки могут возникать из-за того, что родительский каталог не существует, или из-за того, что другие программы одновременно изменяют файлы в файловой системе, создавая то, что называется состоянием гонки.
Состояние гонки происходит, когда два или более программ, необходимо создать файл с таким же именем в том же месте. Если возникает ошибка этого типа, ее очень сложно найти и исправить, поскольку она недетерминирована, или, проще говоря, могут произойти разные вещи в зависимости от точного времени двух гонщиков, соревнующихся за данные.
В этой статье мы увидим, как создать подкаталог в Python безопасным способом, шаг за шагом.
Безопасное создание вложенного каталога с помощью pathlib
Существует множество способов создания подкаталога, но, возможно, самым простым является использование модуля pathlib
. Модуль pathlib
в первую очередь предназначен для того, чтобы помочь абстрагировать различные файловые системы операционной системы и обеспечить единый интерфейс для работы с большинством из них.
Благодаря этому ваш код должен быть независимым от платформы. Обратите внимание, что это работает только в более новых версиях Python (3.5 и выше).
Допустим, у нас есть абсолютный путь к каталогу, данный нам в виде строки, и мы хотим создать подкаталог с заданным именем. Давайте создадим каталог с именем OuterDirectory
и поместим в него InnerDirectory
.
Мы импортируем Path
из модуля pathlib
, создадим объект Path
с желаемым путем для нашего нового файла и воспользуемся методом mkdir()
, имеющим следующую сигнатуру:
Path.mkdir(mode=0o777, parents=False, exist_ok=False)
Следующий фрагмент кода выполняет то, что мы описали выше:
from pathlib import Path # Import the module
path = Path("/home/kristina/OuterDirectory/InnerDirectory") # Create Path object
path.mkdir() # Cake the directory
В случае неудачи mkdir()
каталог не будет создан и возникнет ошибка.
Параметры и ошибки mkdir()
Если вы запустите код без создания OuterDirectory
, вы увидите следующую ошибку:
Traceback (most recent call last):
File "makesubdir.py", line 3, in <module>
path.mkdir()
File "/home/kristina/anaconda3/lib/python3.7/pathlib.py", line 1230, in mkdir
self._accessor.mkdir(self, mode)
FileNotFoundError: [Errno 2] No such file or directory: '/home/kristina/OuterDirectory/InnerDirectory'
Или, если InnerDirectory
уже существует:
Traceback (most recent call last):
File "/home/kristina/Desktop/UNM/makesubdir.py", line 3, in <module>
path.mkdir()
File "/home/kristina/anaconda3/lib/python3.7/pathlib.py", line 1230, in mkdir
self._accessor.mkdir(self, mode)
FileExistsError: [Errno 17] File exists: '/home/kristina/OuterDirectory/InnerDirectory'
Если каталог уже существует, возникнет ошибка FileExistsError
, а если родительский каталог не существует, будет вызвано FileNotFoundError
.
Поскольку мы не хотим, чтобы наша программа прерывалась всякий раз, когда она сталкивается с такой ошибкой, мы поместим этот код в блок try:
from pathlib import Path
path = Path("/home/kristina/OuterDirectory/InnerDir")
try:
path.mkdir()
except OSError:
print("Failed to make nested directory")
else:
print("Nested directory made")
При запуске, если каталог успешно создан, вывод будет:
Nested directory made
Если мы столкнемся с ошибками, будет выведено следующее:
Failed to make a nested directory
Метод mkdir()
принимает три параметра: mode
, parents
, и exit_ok
.
- Параметр
mode
, если он задан, в сочетании сumask
указывает, какие пользователи имеют права на чтение, запись и выполнение. По умолчанию все пользователи имеют все привилегии, которые могут быть не тем, что нам нужно, если речь идет о безопасности. Мы еще поговорим об этом позже. parents
указывает, что в случае отсутствия родительского каталога, должен ли метод:Создайте сам отсутствующий родительский каталог (true
)Или вызвать ошибку, как в нашем втором примере (false
)exist_ok
указывает, следует ли вызыватьFileExistsError
, если каталог с таким же именем уже существует. Обратите внимание, что эта ошибка все равно будет возникать, если файл с тем же именем не является каталогом.
Назначение прав доступа
Давайте создадим каталог с именем SecondInnerDirectory
, в котором только владелец имеет все права на чтение, запись и выполнение, внутри несуществующего SecondOuterDirectory
:
from pathlib import Path
path = Path("/home/kristina/SecondOuterDirectory/SecondInnerDirectory")
path.mkdir(mode = 0o007, parents= True, exist_ok= True)
Это должно выполняться без ошибок. Если мы перейдем к SecondOuterDirectory
и проверим его содержимое с консоли следующим образом:
ls -al
У нас должен получиться результат:
total 12
drwxrwxr-x 3 kristina kristina 4096 dec 10 01:26 .
drwxr-xr-x 77 kristina kristina 4096 dec 10 01:26 ..
d------r-x 2 kristina kristina 4096 dec 10 01:26 SecondInnerDirectory
Итак, мы видим, что родительский каталог был успешно создан, но привилегии не такие, как ожидалось. Владелец не имеет права на запись.
Проблема в том, что umask
мы не можем создать желаемые привилегии. Чтобы обойти это, мы сохраним исходное значение umask
, временно изменим его и, наконец, вернем его к исходному значению с помощью метода umask()
из модуля ОС. umask()
возвращает старое значение umask
.
Давайте перепишем наш код, чтобы проверить это:
from pathlib import Path
import os
old_mask = os.umask(0) # Saving the old umask value and setting umask to 0
path = Path("/home/kristina/SecondOuterDirectory/SecondInnerDirectory")
path.mkdir(mode = 0o007, parents= True, exist_ok= True)
os.umask(old_mask) # Reverting umask value
Выполнение этого кода и повторное использование команды ls -al
приведет к следующему результату:
total 12
drwxrwxrwx 3 kristina kristina 4096 dec 10 01:45 .
drwxr-xr-x 77 kristina kristina 4096 dec 10 01:45 ..
d------rwx 2 kristina kristina 4096 dec 10 01:45 SecondInnerDirectory