Работа с файловой системой

Взаимодействие c OC
Python реализует кроссплатформенное взаимодействие с ОС через модуль os. Этот модуль содержит интерфейс для многих функций, зависящих от операционной системы, для управления процессами, файлами, файловыми дескрипторами, каталогами и другими «низкоуровневыми» функциями ОС. Изучить все возможности модуля можно в документации или в русскоязычной версии тут.

Работа с файловой системой

Файловая система организовывает хранение и доступ данным. Данные хранятся в виде множества файлов, а файловая система позволяет организовать хранение в виде иерархического дерева из каталогов с файлами. Каждый файл в этом дереве идентифицируется путем от корня дерева (ФС).

Путь - это уникальный идентификатор файла в ФС, состоит из цепочки каталогов до файла.

Абсолютный путь представляет из себя цепочку всех каталогов от корня ФС до имени файла. Пример:
/home/user/music/file.mp3  # unix
D:\\music\\file.mp3        # windows
Относительный путь - это цепочка каталогов до имени файла относительно текущего каталога (а не от корня ФС). Пример:
# Пусть мы находимся в каталоге пользователя
/home/user # unix
D:\\        # windows
# Тогда относительный путь до файла будет таким:
music/file.mp3 # unix
music\\file.mp3 # windows
# Путь до текущего каталога + относительный путь = абсолютный путь
Процесс работы с ФС предполагает возможность движения по веткам дерева ФС. Тот в каталог в котором мы находимся в данный момент называется рабочим каталогом

В Python для работы с путями существует модуль os.path. Изучить все возможности модуля можно в документации, или в русскоязычной версии тут.

Создание каталогов и файлов
import os
# возвращает абсолютный путь до текущего (рабочего) каталога
current_path = os.getcwd() # /home/user

# соединяет пути с учетом правил ОС
target_path = os.path.join(current_path, 'music') # /home/user/music/

# Проверяет что такой путь существует в ФС
target_path_exists = os.path.exists(target_path) # False

if not target_path_exists:
    # Создает указанный путь из каталогов
    # mode - выставляет права пользователей для каталога
    # o777 - означает что всем можно читать, писать и выполнять файлы каталога
    # exist_ok - не вызывает ошибку если путь уже существует
    os.makedirs(target_path, mode=0o777, exist_ok=True)
    target_path_exists = os.path.exists(target_path) # True

# Проверяет что у текущего пользователя есть права на запись по этому пути
write_permission = os.access(target_path, mode=os.W_OK) # True
if write_permission:
    # Создадим путь /home/user/music/file.mp3
    file_path = os.path.join(target_path, 'file.mp3')
    # Создадим файл по созданному пути
    with open(file_path, 'wb') as file:
        file.write(b"Bon Jovi - It's My Life")
Изменение рабочего каталога
import os

current_path = os.getcwd() # /home/user/
target_path = os.path.join(current_path, 'music') # /home/user/music/

# Изменим рабочий каталог на указанный 
os.chdir(target_path)
current_path = os.getcwd() # /home/user/music/

# Директория на уровень выше 
up_level_path = os.path.dirname(os.getcwd()) # /home/user/
# Изменим рабочий каталог на уровень выше
os.chdir(up_level_path)
current_path = os.getcwd() # /home/user/
Изменение каталогов и файлов
# -*- coding utf-8 -*-
import os

# /home/user/
# ---- ---- music/
# ---- ---- ---- file.mp3

current_path = os.getcwd() # /home/user/

# Переименуем каталог music в музыка
old_path_name = os.path.join(current_path, 'music') # /home/user/music/
new_path_name = os.path.join(current_path, 'музыка') # /home/user/музыка/
os.rename(old_path_name, new_path_name)

# Переименуем файл music в музыка
old_path_name = os.path.join(current_path, 'музыка', 'file.mp3') # /home/user/music/file.mp3
new_path_name = os.path.join(current_path, 'музыка', "It's my life.mp3") # /home/user/music/It's my life.mp3
os.rename(old_path_name, new_path_name)
Перемещение каталогов и файлов
# -*- coding utf-8 -*-
import os

# /home/user/
# ----- ---- музыка/
# ----- ---- ---- It's my life.mp3

current_path = os.getcwd() # /home/user/

old_path_name = os.path.join(current_path, 'музыка', "It's my life.mp3")
new_path_name = os.path.join(current_path, 'music', 'Bon Jovi', "It's my life.mp3")
# Перемещаем файл в новую директорию
# /home/user/музыка/It's my life.mp3 -> /home/user/music/Bon Jovi/It's my life.mp3
os.renames(old_path_name, new_path_name)

# /home/user/
# ---- ---- music/
# ---- ---- ----- Bon Jovi/
# ---- ---- ----- ------- It's my life.mp3
Метод os.renames() перенесет файл в целевую директорию по следующим правилам:

  • Рекурсивно создаст недостающие директории
  • Не перезапишет целевой файл если он существует
  • Удалит старую директорию, если она пуста
Удаление файлов и каталогов
# -*- coding utf-8 -*-
import os

# /home/user/
# ---- ---- music/
# ---- ---- ----- Bon Jovi/
# ---- ---- ----- ------- It's my life.mp3

current_path = os.getcwd() # /home/user/

# относительна директория
path = os.path.join('music', 'Bon Jovi', "It's my life.mp3") # music/Bon Jovi/It's my life.mp3
# удаление файла It's my life.mp3
os.remove(path)
# Теперь остались два пустых каталога "music" и "Bon Jovi":
# /home/user/
# ---- ---- music/
# ---- ---- ----- Bon Jovi/

# Удалим пустые каталоги рекурсивно
# Для этого получим каталог удаленного файла
file_dir = os.path.dirname(path)  # music/Bon Jovi/
os.removedirs(file_dir)
Метод os.remove() удаляет только файл по указанному пути, если он существует, иначе будет поднято исключение FileNotFoundError

Метод os.rmdir() удаляет только пустую диреткрию по указанному пути

Метод os.removedirs() рекурсивно удаляет только пустые диреткрии по указанному пути


Обработка исключений

Выполнение недопустимых команд с ФС будет приводить к различным исключениям "семейства" OSError.Можно использовать инструкцию обработки исключений, например так:
try:
    with open(file_path):
        text = file.read()
except PermissionError:
    # Обработать ошибку
Это горомоздкий способ в стиле EAFP, но он является кроссплатформенным.

Модуль os поддерживет встроенный метод access в стиле LBYL, который, к сожалению работает только для POSIX (UNIX/LINUX) операционных систем, потому используйте его в ситуации, когда код расчитан только на POSIX системы. Смотрим пример:
# Перед открытием файла убедиться что у текущего пользователя есть права на чтение файла
if os.access(file_path, os.R_OK):
    with open(file_path) as file:
        text = file.read()
        
# Аналогично для записи и выполнения:
os.access(file_path, os.W_OK) # True/False
os.access(file_path, os.X_OK) # True/False
Модуль os так же поддерживает методы проверки существования пути, а так же что путь указывает на каталог или файл:
# Проверка что пусть существует в стиле EAFP
path = '/home/user/music/'
try:
    os.chdir(path)
except FileNotFoundError:
    print(f'Каталог не найден: {dir_path}')

# Проверка что пусть существует в стиле LBYL
if os.path.exists(): # True/False
    os.chdir(path)
else:
    print(f'Каталог не найден: {dir_path}')

# Аналогично:
# Убедиться что путь указывает на существующий каталог
os.path.isdir() # True/False

# Убедиться что путь указывает на существующий файл
os.path.isfile() # True/False