Найти в Дзене
PythonTalk

os.path vs pathlib в Python: объясняю, почему один модуль пора выкинуть на свалку

Если вы работаете с файлами и папками в Python, у вас есть два пути. Первый — это модуль os.path, старый, как дискеты, и знакомый каждому, кто начинал программировать больше 10 лет назад. Второй — современный, объектно-ориентированный модуль pathlib, появившийся в Python 3.4. Многие до сих пор сидят на первом варианте по принципу «и так работает». Но сегодня я объясню, почему цепляться за os.path — это не просто дело вкуса, а настоящая ошибка, которая крадет ваше время и добавляет коду хрупкости. На первый взгляд, все просто. os.path — это набор функций. Дали функции строки — получили на выходе строку. Но дьявол, как всегда, в деталях. Главная проблема — os.path работает со строками, а не с путями как с сущностями. Вы постоянно склеиваете строки вручную или через функцию os.path.join. Это прямой путь к багам. Забыли правильный слэш для вашей ОС, где-то склеили лишний разделитель — и вот вы уже ловите ошибку FileNotFoundError и не понимаете, в чем дело. Каждый раз, когда вы соединяете ч
Оглавление

Если вы работаете с файлами и папками в Python, у вас есть два пути. Первый — это модуль os.path, старый, как дискеты, и знакомый каждому, кто начинал программировать больше 10 лет назад. Второй — современный, объектно-ориентированный модуль pathlib, появившийся в Python 3.4.

Многие до сих пор сидят на первом варианте по принципу «и так работает». Но сегодня я объясню, почему цепляться за os.path — это не просто дело вкуса, а настоящая ошибка, которая крадет ваше время и добавляет коду хрупкости.

Чем на самом деле плох «классический» os.path

На первый взгляд, все просто. os.path — это набор функций. Дали функции строки — получили на выходе строку. Но дьявол, как всегда, в деталях.

1. Строковый ад.

Главная проблема — os.path работает со строками, а не с путями как с сущностями. Вы постоянно склеиваете строки вручную или через функцию os.path.join. Это прямой путь к багам. Забыли правильный слэш для вашей ОС, где-то склеили лишний разделитель — и вот вы уже ловите ошибку FileNotFoundError и не понимаете, в чем дело.

Каждый раз, когда вы соединяете части пути как обычные строки, где-то в мире грустит один Гвидо ван Россум.

2. Многословность и слепота.

Хотите собрать путь из двух частей и проверить, существует ли он? Вам нужно импортировать модуль os, вызвать функцию os.path.join, а затем передать результат в os.path.exists. Куча действий для элементарной операции. Модуль просто выполняет операции над строками, он «слеп» и понятия не имеет, что происходит в файловой системе, пока вы не спросите его отдельной функцией.

Новый подход: сила объектов в pathlib

А теперь посмотрим, как эту же головную боль решает pathlib. Вместо бездушных строк у нас появляются умные объекты Path. И это меняет абсолютно все.

1. Интуитивно понятный синтаксис.

Больше не нужно вызывать os.path.join. Для соединения путей используется обычный оператор деления /. Это не просто красиво, это чертовски логично и читаемо.
Сравните:

  • Старый подход: full_path = os.path.join('data', 'reports', 'report.txt')
  • Новый подход: p = Path('data') / 'reports' / 'report.txt'

Результат тот же, а читается в разы легче.

[ИНСТРУКЦИЯ ДЛЯ ДЗЕН: Вставить тематическую диаграмму или яркий мем, иллюстрирующий сравнение простоты pathlib и сложности os.path. Например, мем с Дрейком: отворачивается от os.path.join, одобряет Path / 'filename'.]

2. Все под рукой.

Объект Path — это не просто строка, это полноценный представитель файла или папки. Он сам про себя все знает. Хотите проверить существование? Вызовите у него метод p.exists(). Хотите прочитать содержимое текстового файла? Метод p.read_text(). Получить родительскую папку? Свойство p.parent. Никаких лишних импортов и вызовов внешних функций. Все методы логично сгруппированы в одном месте.

Нокаутирующий удар: реальная задача

Хватит теории, давайте к практике. Вот задача, которая моментально расставляет все по своим местам.

Задача: найти все файлы с расширением .py во всех подпапках, начиная с текущей.

Решение на os.path:

Здесь нам понадобится еще один модуль, os, и его функция walk. Код будет выглядеть громоздко: нужно запустить цикл, который обходит дерево папок, потом в нем запустить еще один цикл, который перебирает файлы, проверить у каждого расширение и потом еще склеить полный путь через os.path.join. Получается вложенная, не самая очевидная конструкция.

Решение на pathlib:

Готовы?
py_files = list(Path('.').rglob('*.py'))

Всё. Одна строчка. Один метод rglob (рекурсивный glob), который делает именно то, что нам нужно. Он интуитивно понятен и решает задачу элегантно и без лишнего кода.

Комментарии здесь, как говорится, излишни.

Вердикт: os.path пора на свалку истории?

Почти. Объективно, есть всего два случая, когда использование os.path еще можно оправдать:

  1. Поддержка древнего легаси. Если вы работаете с кодом, который должен запускаться на Python версии ниже 3.4 (например, на Python 2.7), то у вас просто нет выбора. Но будем честны, в 2024 году это крайне редкая ситуация.
  2. Микрооптимизации. Если вам в цикле на миллионы итераций нужно выполнить одну-единственную операцию (например, получить имя файла из пути), то создание полноценного объекта Path может быть чуть медленнее, чем прямой вызов функции os.path.basename(). Но выигрыш будет в наносекундах, и заметить его можно лишь в очень специфических, высоконагруженных системах.

Во всех остальных 99,9% случаев pathlib — это ваш выбор по умолчанию.

Пишете новый проект? Только pathlib.
Делаете небольшой скрипт для автоматизации рутины?
pathlib.
Сомневаетесь, что выбрать?
pathlib.

Время, которое вы сэкономите на разработке и отладке благодаря читаемому и безопасному коду, в тысячи раз ценнее этих мнимых наносекунд производительности.

Кстати, подобные разборы, а также полезные инструменты и задачи я публикую в своем Telegram-канале PythonTalk.

А теперь ваш ход. Вы уже полностью перешли на pathlib или по старинке склеиваете строки? Делитесь своим опытом и аргументами в комментариях