До недавнего времени работа с файловой системой в 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».
