До недавнего времени работа с файловой системой в Python была довольно сложна. Разработчики часто боролись с неправильными путями к файлам, в которые закрадывались ошибки, поскольку в качестве входных данных требовались длинные строки. Кроме того, код часто ломался из-за несоответствий в разных операционных системах.
Примечание редакции: предлагаем почитать “Как прописать на Python путь к файлу в Windows, Mac и Linux”.
К счастью, в версии Python 3.4 в стандартную библиотеку был добавлен модуль pathlib. Он предоставляет элегантное решение для работы с путями файловой системы с использованием долгожданного объектно-ориентированного подхода, а также обеспечивает платформонезависимость.
Это руководство познакомит вас с возможностями модуля pathlib, которые помогут вам в повседневной работе с файловой системой. Используя pathlib, вы получите преимущества эффективных рабочих процессов и простого поиска данных.
Содержание
- Сравнение модулей os и pathlib
- Работа с объектами Path
- Работа с компонентами пути
- Распространенные операции с путями
- Продвинутые манипуляции с путями
- Работа с файлами
- Заключение
Сравнение модулей os и pathlib
До Python 3.4 традиционным способом работы с путями к файлам было использование модуля os. Хотя этот модуль когда-то был очень эффективным, его возраст стал сказываться.
Мы можем продемонстрировать уникальную ценность pathlib, рассмотрев распространенную задачу в области науки о данных: поиск всех png-файлов внутри заданной директории и всех ее поддиректорий.
Примечание редакции: на эту тему у нас есть отдельная статья – “Как на Python найти файлы, имеющие определенное расширение”.
Если бы мы использовали модуль os, мы могли бы написать следующий код:
import os dir_path = "/home/user/documents" files = [ os.path.join(dir_path, f) for f in os.listdir(dir_path) if os.path.isfile(os.path.join(dir_path, f)) and f.endswith(".png") ]
Хотя этот код решает непосредственную задачу поиска наших png-файлов, он выявляет несколько основных недостатков модуля os. Во-первых, код почти нечитабельный, а с учетом относительной простоты операции это очень огорчительно. Во-вторых, наш код предполагает знание представлений списков, а это не следует воспринимать как должное. В-третьих, в коде задействованы строковые операции, которые чреваты ошибками. И вдобавок ко всему код не очень лаконичен.
Если бы вместо этого мы использовали модуль pathlib, наш код был бы намного проще. Как мы уже говорили, pathlib предоставляет объектно-ориентированный подход к работе с путями файловой системы. Взгляните:
from pathlib import Path # Создать объект path dir_path = Path(dir_path) # Найти все png-файлы в директории files = list(dir_path.glob("*.png"))
Объектно-ориентированное программирование организует код вокруг объектов и их взаимодействий, что приводит к созданию более модульного, переиспользуемого и удобного в обслуживании кода.
Работа с объектами Path
Библиотека pathlib вращается вокруг так называемых объектов Path
, которые представляют пути к файловой системе в структурированном и независимом от платформы виде.
Вы уже видели, как мы перенесли класс Path
из модуля pathlib в наше текущее пространство имен с помощью следующей строки кода:
from pathlib import Path
После вызова класса Path
из pathlib мы можем создавать объекты Path
несколькими способами, в том числе из строк, из других объектов Path
, из текущей рабочей директории и из домашней директории.
Давайте рассмотрим каждый из этих способов.
Создание объектов Path из строк
Мы можем создать объект Path
, передав в переменную строку, представляющую путь к файловой системе. Это преобразует строковое представление пути к файлу в объект Path
.
file_path_str = "data/union_data.csv" data_path = Path(file_path_str)
Создание объектов Path из других объектов Path
Существующие объекты Path
могут служить строительными блоками для создания новых путей.
Для этого нужно объединить базовый путь, директорию «data» и имя файла в один путь к файлу. Для расширения объектов Path
не забывайте использовать прямую косую черту там, где это необходимо.
base_path = Path("/home/user") data_dir = Path("data") # Комбинирование нескольких путей file_path = base_path / data_dir / "prices.csv" print(file_path) # Результат: # '/home/user/data/prices.csv'
Создание объектов Path из текущей рабочей директории
Здесь мы присваиваем текущую директорию переменной cwd
с помощью метода Path.cwd()
. После этого мы можем получить путь к текущей директории, в которой запущен наш скрипт.
cwd = Path.cwd() print(cwd) # Результат: # '/home/bexgboost/articles/2024/4_april/8_pathlib'
Создание объектов Path из домашней директории
Мы можем создать путь, объединив нашу домашнюю директорию с дополнительными подкаталогами. Здесь мы объединяем домашнюю директорию с подкаталогами “downloads” и “projects”.
home = Path.home() home / "downloads" / "projects" # Результат: # PosixPath('/home/bexgboost/downloads/projects')
Важное замечание. Класс Path
сам по себе не выполняет никаких операций с файловой системой, таких как проверка пути, создание каталогов или файлов. Он предназначен для представления и манипулирования путями. Для реального взаимодействия с файловой системой (проверка существования, чтение/запись файлов) нам придется использовать специальные методы объектов Path
, а для некоторых сложных случаев – обратиться за помощью к модулю os.
Работа с компонентами пути с помощью pathlib
Атрибуты пути – это различные свойства и компоненты пути к файлу, которые помогают в идентификации и управлении файлами и каталогами в файловой системе. Подобно тому, как физический адрес состоит из различных частей, таких как номер улицы, город, страна и почтовый индекс, путь файловой системы можно разбить на более мелкие компоненты. pathlib позволяет нам получать доступ к этим компонентам и манипулировать ими с помощью атрибутов пути через точечную нотацию.
Работа с корневой директорией
Корень – это самый верхний каталог в файловой системе. В Unix-подобных системах он обозначается прямой косой чертой (/
). В Windows это обычно буква диска, за которой следует двоеточие, например C:
.
image_file = home / "downloads" / "midjourney.png" image_file.root # Результат: # '/'
Работа с родительской директорией
Родительская директория содержит текущий файл или каталог. Она находится на один уровень выше относительно текущей директории или файла.
image_file.parent # PosixPath('/home/bexgboost/downloads')
Работа с полным именем файла
Атрибут name
возвращает полное имя файла, включая расширение, в виде строки.
image_file.name # 'midjourney.png'
Работа с суффиксом файла
Атрибут suffix
возвращает расширение файла, включая точку, в виде строки (или пустую строку, если расширение отсутствует).
image_file.suffix # '.png'
Работа с собственно именем файла
Атрибут stem
возвращает имя файла без расширения. Это может быть полезно при конвертации файлов в различные форматы.
image_file.stem # 'midjourney'
Примечание. На Mac пути к файлам чувствительны к регистру, поэтому /Users/username/Documents
и /users/username/documents
– разные файлы.
Атрибут parts
Мы можем использовать атрибут parts
, чтобы разделить объект Path
на составляющие.
image_file.parts # ('/', 'home', 'bexgboost', 'downloads', 'midjourney.png')
Атрибут parents
Атрибут parents
, возвращающий генератор, превращает компоненты пути в объекты Path
.
list(image_file.parents)
Результат:
[PosixPath('/home/bexgboost/downloads'), PosixPath('/home/bexgboost'), PosixPath('/home'), PosixPath('/')]
Распространенные операции с путями
Объекты Path
имеют множество методов, которые позволяют эффективно взаимодействовать с каталогами и их содержимым. Давайте рассмотрим, как выполнять некоторые из наиболее распространенных операций.
Вывод списков каталогов
Метод iterdir()
позволяет перебирать все файлы и подкаталоги в папке. Он особенно полезен для обработки всех файлов в каталоге или выполнения операций над каждой записью.
cwd = Path.cwd()
for entry in cwd.iterdir(): # Process the entry here ... # print(entry)
Поскольку метод iterdir()
возвращает итератор, записи извлекаются по требованию по мере выполнения цикла.
Метод is_dir()
is_dir()
возвращает True, если путь указывает на директорию, и False в противном случае.
for entry in cwd.iterdir(): if entry.is_dir(): print(entry.name)
.ipynb_checkpoints data images
Метод is_file()
is_file()
возвращает True, если путь указывает на файл, False – в противном случае.
for entry in cwd.iterdir(): if entry.is_file(): print(entry.suffix)
.ipynb .txt
Метод exists()
Поскольку объекты Path
только представляют пути, иногда необходимо проверить, существует ли путь, используя метод .exists()
:
image_file.exists() # False
Метод .exists()
проверяет, существует ли путь. Это полезно, так как объекты Path могут представлять файлы и каталоги, которых фактически может и не быть в файловой системе.
Создание и удаление путей
Модуль pathlib также предлагает функции для создания и удаления файлов и директорий. Давайте посмотрим, как это делается.
Метод .mkdir()
mkdir()
создает новую директорию по указанному пути. По умолчанию он создает директорию в текущей рабочей директории.
from pathlib import Path data_dir = Path("new_data_dir") # Создать директорию 'new_data_dir' в текущей рабочей директории data_dir.mkdir()
Когда вы хотите создать структуру директорий, в которой некоторые родительские директории могут не существовать, можно передать в метод mkdir
аргумент(parents=True)
. Установка parents=True
гарантирует, что все необходимые родительские директории будут созданы в процессе работы.
sub_dir = Path("data/nested/subdirectory") # Создать 'data/nested/subdirectory', даже если 'data' или 'nested' не существуют sub_dir.mkdir(parents=True)
Помните, что mkdir()
вызывает исключение, если директория с таким же именем уже существует.
Path('data').mkdir() # FileExistsError: [Errno 17] File exists: 'data'
Метод unlink()
Метод unlink()
безвозвратно удаляет файл, представленный объектом Path
. Чтобы не получить ошибку, рекомендуется перед запуском этого метода проверить, существует ли файл.
to_delete = Path("data/prices.csv") if to_delete.exists(): to_delete.unlink() print(f"Successfully deleted {to_delete.name}") # Результат: # Successfully deleted prices.csv
Метод rmdir()
Метод rmdir()
удаляет пустую директорию. Помните, что rmdir()
работает только для удаления пустых директорий. Самый простой способ удалить непустую директорию – использовать библиотеку shutil или терминал.
empty_dir = Path("new_data_dir") empty_dir.rmdir()
Примечание. Будьте осторожны при использовании unlink()
или rmdir()
, так как результаты их работы необратимы.
Продвинутые манипуляции с путями
Давайте перейдем к некоторым продвинутым концепциям манипулирования путями и рассмотрим, как применить их на практике с помощью pathlib.
Относительные и абсолютные пути
Начнем с понимания различий между абсолютными и относительными путями.
Относительные пути
Относительные пути указывают местоположение файла или директории относительно текущей директории. Они короткие и гибкие в рамках вашего проекта, но могут запутать, если вы измените рабочий каталог.
Например, в моей текущей рабочей директории есть папка “images”, в которой находится файл midjourney.png.
image = Path("images/midjourney.png") image # PosixPath('images/midjourney.png')
Приведенный выше код работает сейчас, но если я перемещу используемый блокнот в другое место, код сломается, потому что папка “images” не переместится вместе с блокнотом.
Абсолютные пути
Абсолютные пути указывают полное местоположение файла или директории относительно корня файловой системы. Они не зависят от текущей директории и обеспечивают четкую точку отсчета для любого пользователя в любой точке системы.
image_absolute = Path("/home/bexgboost/articles/2024/4_april/8_pathlib/images/midjourney.png") image_absolute # PosixPath('/home/bexgboost/articles/2024/4_april/8_pathlib/images/midjourney.png')
Как видите, абсолютные пути могут быть довольно длинными, особенно в сложных проектах со вложенными древовидными структурами. По этой причине большинство людей предпочитают более короткие относительные пути.
Метод resolve()
Модуль pathlib позволяет преобразовывать относительные пути в абсолютные с помощью метода resolve()
.
relative_image = Path("images/midjourney.png") absolute_image = relative_image.resolve() absolute_image # PosixPath('/home/bexgboost/articles/2024/4_april/8_pathlib/images/midjourney.png')
Можно осуществить и обратное преобразование. Если у нас есть абсолютный путь, мы можем преобразовать его в относительный на основе указанной директории.
relative_path = Path.cwd() absolute_image.relative_to(relative_path) # PosixPath('images/midjourney.png')
Применение шаблонов
Чтобы проиллюстрировать работу с шаблонами, мы можем вернуться к примеру, который мы представили в начале статьи, когда написали код для поиска всех png-файлов в заданной директории.
files = list(dir_path.glob("*.png"))
Для эффективного поиска файлов, соответствующих определенному шаблону, в любой директории pathlib использует встроенный модуль glob
. Этот модуль очень полезен при обработке файлов с похожими именами или расширениями.
Метод glob()
принимает на вход строку шаблона, содержащую символы подстановки, и возвращает объект-генератор, который по запросу выдает подходящие объекты Path
.
Символы подстановки и то, чему они соответствуют:
*
– ноль или более символов?
– любой одиночный символ[]
определяет диапазон символов (например, [a-z] – любая строчная буква).
Для примера попробуем найти все Jupyter-блокноты в моей директории «articles».
articles_dir = Path.home() / "articles" # Найти все скрипты notebooks = articles_dir.glob("*.ipynb") # Вывести число найденных print(len(list(notebooks))) # Результат: # 0
Метод .glob()
не нашел ни одного блокнота, что на первый взгляд кажется удивительным, ведь я написал более 150 статей. Причина в том, что .glob()
ищет только внутри заданной директории, но не в ее поддиректориях.
Решить эту проблему можно с помощью рекурсивного поиска, для чего необходимо использовать метод rglob()
, который имеет схожий синтаксис:
notebooks = articles_dir.rglob("*.ipynb") print(len(list(notebooks))) # Результат: # 357
На этот раз наш код нашел все 357 файлов.
Работа с файлами с помощью pathlib
Как мы видели, объекты Path
только представляют файлы. Но у них есть и определенные методы для распространенных операций с файлами. В этом разделе мы рассмотрим, как их использовать.
Чтение файлов
Чтение содержимого файлов является фундаментальной операцией во многих приложениях Python. pathlib предоставляет удобные сокращенные методы для чтения файлов как в виде текста, так и в виде байтов.
Метод read_text()
позволяет нам прочитать содержимое текстового файла и закрыть его.
file = Path("file.txt") print(file.read_text()) # Результат: # This is sample text.
Для бинарных файлов вместо него можно использовать метод read_bytes()
.
image = Path("images/midjourney.png") image.read_bytes()[:10] # Результат: # b'\x89PNG\r\n\x1a\n\x00\x00'
Помните, что при использовании метода read_*
важна обработка ошибок:
nonexistent_file = Path("gibberish.txt") try: contents = nonexistent_file.read_text() except FileNotFoundError: print("No such thing.") # Результат: # No such thing.
Запись файлов
Запись в файлы так же проста, как и чтение. Для записи файлов у нас есть метод write_text()
.
file = Path("file.txt") file.write_text("This is new text.") # Результат: # 17
file.read_text() # Результат: # 'This is new text.'
Как видите, метод write_text()
перезаписывает текст. Хотя для write_text()
нет режима добавления, мы можем использовать read_text()
и write_text()
вместе, чтобы добавить текст в конец файла.
old_text = file.read_text() + "\n" final_text = "This is the final text." # Скомбинировать старый и новый текст и записать их обратно file.write_text(old_text + final_text) print(file.read_text()) # Результат: # This is new text. # This is the final text.
Функция write_bytes()
работает аналогичным образом. Для примера давайте сначала продублируем изображение midjourney.png с новым именем.
original_image = Path("images/midjourney.png") new_image = original_image.with_stem("duplicated_midjourney") new_image # Результат: # PosixPath('images/duplicated_midjourney.png')
Метод with_stem()
возвращает путь к файлу с другим именем (хотя суффикс остается прежним). Это позволяет нам читать оригинальное изображение и записывать его контекст в новое изображение.
new_image.write_bytes(original_image.read_bytes()) # 1979612
Переименование и перемещение файлов
В дополнение к функции with_stem()
для изменения имени файла pathlib предлагает метод rename()
для более полного переименования.
file = Path("file.txt") target_path = Path("new_file.txt") file.rename(target_path) # Результат: # PosixPath('new_file.txt')
rename()
принимает целевой путь, который может быть строкой или другим объектом Path
.
Для перемещения файлов можно использовать функцию replace()
, которая также принимает целевой путь:
# Define the file to be moved source_file = Path("new_file.txt") # Define the location to put the file destination = Path("data/new/location") # Create the directories if they don't exist destination.mkdir(parents=True) # Move the file source_file.replace(destination / source_file.name) # Результат: # PosixPath('data/new/location/new_file.txt')
Создание пустых файлов
pathlib позволяет создавать пустые файлы с помощью метода touch
:
# Define new file path new_dataset = Path("data/new.csv") new_dataset.exists() # False
new_dataset.touch() new_dataset.exists() # True
Метод touch
изначально предназначен для обновления времени модификации файла, поэтому его можно использовать и для существующих файлов.
original_image.touch()
Если вам нужно зарезервировать имя файла для последующего использования, но в данный момент нет никакого содержимого для записи в него, можно использовать touch
для создания болванки. Этот метод создали, вдохновляясь командой touch
терминала Unix.
Разрешения и информация о файловой системе
В качестве заключительного пункта давайте разберем, как получить доступ к характеристикам файла с помощью метода .stat()
. Если вы знакомы с модулем os, то заметите, что этот метод имеет тот же вывод, что и os.stat()
.
image_stats = original_image.stat() image_stats # os.stat_result(st_mode=33188, st_ino=1950175, st_dev=2080, st_nlink=1, st_uid=1000, st_gid=1000, st_size=1979612, st_atime=1714664562, st_mtime=1714664562, st_ctime=1714664562)
Мы также можем получить размер файла, используя точечную нотацию.
image_size = image_stats.st_size # File size in megabytes image_size / (1024**2) # 1.8879051208496094
Заключение
Появление модуля pathlib в Python 3.4 значительно упростило разработчикам работу с файловой системой. Благодаря объектно-ориентированному подходу к работе с путями, pathlib предоставляет структурированный и простой способ представления путей к файловой системе. pathlib также предлагает платформенную независимость, так как обрабатывает разделители путей последовательно в различных операционных системах, поэтому наш код не сломается на новой машине. Наконец, pathlib предлагает обширный набор кратких и выразительных методов для распространенных операций с файловой системой.
Перевод статьи «A Comprehensive Guide to Using pathlib in Python For File System Manipulation».