В каждой операционной системе существуют свои правила построения путей к файлам. Например, в Linux для путей используются прямые слэши (“/”), а в Windows — обратные слэши (“\”).
Это незначительное отличие может создать проблемы, если вы занимаетесь проектом и хотите, чтобы другие разработчики, работающие в разных операционных системах, могли дополнить ваш код.
К счастью, если вы пишете на Python, то с этой задачей успешно справляется модуль Pathlib. Он обеспечит одинаковую работу ваших путей к файлам в разных операционных системах. Кроме того, он предоставляет функциональные возможности и операции, которые помогут вам сэкономить время при обработке и манипулировании путями.
Необходимые условия
Pathlib по умолчанию поставляется с Python >= 3.4. Однако, если вы используете версию Python ниже 3.4, у вас не будет доступа к этому модулю.
Как работает Pathlib?
Чтобы разобраться, как можно построить базовый путь с помощью Pathlib, давайте создадим новый файл Python example.py
и поместим его в определенный каталог.
Откройте файл и введите следующее:
import pathlib
p = pathlib.Path(__file__)
print(p)
example.py
В данном примере мы импортируем модуль Pathlib. Затем создаем новую переменную p
, чтобы сохранить путь. Здесь мы используем объект Path из Pathlib со встроенной в Python переменной под названием __file__
. Эта переменная служит ссылкой на путь к файлу, в котором мы ее прописываем, а именно, example.py
.
Если мы выведем p
, то получим путь к файлу, в котором сейчас находимся:
/home/rochdikhalid/dev/src/package/example.py
Выше показано, что Pathlib создает путь к этому файлу, помещая этот конкретный скрипт в объект Path. Pathlib содержит множество объектов, таких как PosixPath()
и PurePath()
, о которых мы узнаем больше в следующих разделах.
Pathlib делит пути файловой системы на два разных класса, которые представляют два типа объектов пути: Pure Path и Concrete Path.
Pure path предоставляет утилиты для обработки пути к файлу и манипулирования им без совершения операций записи, в то время как Concrete path позволяет производить обработку пути к файлу и выполнять операции записи.
Другими словами, Concrete path — это подкласс Pure path. Он наследует манипуляции от родительского класса и добавляет операции ввода/вывода, которые выполняют системные вызовы.
Pure path в Python
Pure path управляет путем к файлу на вашей машине, даже если он принадлежит другой операционной системе.
Допустим, вы работаете в Linux и хотите использовать путь к файлу Windows. Здесь объекты класса Pure path помогут вам обеспечить работу пути на вашей машине с некоторыми базовыми операциями, такими как создание дочерних путей или доступ к отдельным частям пути.
Но Pure path не сможет имитировать некоторые другие операции, такие как создание каталога или файла, потому что вы не находитесь в этой операционной системе.
Как использовать Pure path
Как видно из приведенной выше диаграммы, Pure path состоит из трех классов, которые обрабатывают любой путь к файловой системе на вашем компьютере:
PurePath() — это корневой узел, который обеспечивает операции по обработке каждого объекта пути в Pathlib.
Когда вы инстанцируете PurePath()
, он создает два класса для обработки путей Windows и других, отличных от Windows. PurePath()
создает общий объект пути "agnostic path", независимо от операционной системы, в которой вы работаете.
In [*]: pathlib.PurePath('setup.py')
Out[*]: PurePosixPath('setup.py')
PurePath() в приведенном выше примере создает PurePosixPath()
, поскольку мы предположили, что работаем на машине Linux. Но если вы создадите его на Windows, то получите что-то вроде PureWindowsPath('setup.py')
.
PurePosixPath() — это дочерний узел PurePath(), реализованный для путей файловой системы, отличной от Windows.
In [*]: pathlib.PurePosixPath('setup.py')
Out[*]: PurePosixPath('setup.py')
Если вы инстанцируете PurePosixPath()
в Windows, то не возникнет никакой ошибки, просто потому что этот класс не выполняет системных вызовов.
PureWindowsPath() — это дочерний узел PurePath()
, реализованный для путей файловой системы Windows.
In [*]: pathlib.PureWindowsPath('setup.py')
Out[*]: PureWindowsPath('setup.py')
То же самое относится и к PureWindowsPath()
, поскольку этот класс не предусматривает системных вызовов, следовательно, его инстанцирование не вызовет ошибок для других операционных систем.
Свойства Pure path
Каждый подкласс в PurePath()
предоставляет следующие свойства:
PurePath().parent выводит родительский класс:
In [*]: pathlib.PurePath('/src/goo/scripts/main.py').parent
Out[*]: PurePosixPath('/src/goo/scripts')
В примере выше мы используем свойство .parent
, чтобы получить путь к логическому родителю main.py
.
PurePath().parents[] выводит предков пути:
In [*]: p = pathlib.PurePath('/src/goo/scripts/main.py')
p.parents[0]
Out[*]: PurePosixPath('/src/goo/scripts')
In [*]: p.parents[1]
Out[*]: PurePosixPath('/src/goo')
Вы всегда должны указывать индекс предка в квадратных скобках, как показано выше. В Python 3.10 и выше можно использовать срезы и отрицательные значения индекса.
PurePath().name предоставляет имя последнего компонента вашего пути:
In [*]: pathlib.PurePath('/src/goo/scripts/main.py').name
Out[*]: 'main.py'
В этом примере конечным компонентом пути является main.py
. Таким образом, свойство .name
выводит имя файла main.py
, то есть main с суффиксом .py.
В свою очередь, PurePath().suffix предоставляет расширение файла последнего компонента вашего пути:
In [*]: pathlib.PurePath('/src/goo/scripts/main.py').suffix
Out[*]: '.py'
По сравнению со свойством .name
, .suffix
выводит расширение файла и исключает имя файла.
PurePath().stem выводит только имя конечного компонента вашего пути без суффикса:
In [*]: pathlib.PurePath('/src/goo/scripts/main.py').stem
Out[*]: 'main'
Как видно выше, свойство .stem
исключает суффикс конечного компонента main.p
y и предоставляет только имя файла.
Методы Pure path
Каждый подкласс PurePath()
предоставляет следующие методы:
PurePath().is_absolute() проверяет, является ли ваш путь абсолютным или нет:
In [*]: p = pathlib.PurePath('/src/goo/scripts/main.py')
p.is_absolute()
Out[*]: True
In [*]: o = pathlib.PurePath('scripts/main.py')
o.is_absolute()
Out[*]: False
Обратите внимание, что абсолютный путь состоит из корня и имени диска. В данном случае PurePath()
не позволяет нам узнать имя диска.
Если вы используете PureWindowsPath()
, то можете репрезентовать абсолютный путь, содержащий имя диска, как PureWindowsPath('c:/Program Files')
.
PurePath().is_relative() проверяет, принадлежит ли данный путь другому заданному пути или нет:
In [*]: p = pathlib.PurePath('/src/goo/scripts/main.py')
p.is_relative_to('/src')
Out[*]: True
In [*]: p.is_relative_to('/data')
Out[*]: False
В данном примере заданный путь /src
является частью или принадлежит пути p
, в то время как другой заданный путь /data
выдает False
, поскольку он не имеет никакого отношения к пути p
.
PurePath().joinpath() конкатенирует путь с заданными аргументами (дочерними путями):
In [*]: p = pathlib.PurePath('/src/goo')
p.joinpath('scripts', 'main.py')
Out[*]: PurePosixPath('/src/goo/scripts/main.py')
Обратите внимание, что нет необходимости добавлять слэши в заданные аргументы, так как метод .joinpath()
делает это за вас.
PurePath().match() проверяет, соответствует ли путь заданному шаблону:
In [*]: pathlib.PurePath('/src/goo/scripts/main.py').match('*.py')
Out[*]: True
In [*]: pathlib.PurePath('/src/goo/scripts/main.py').match('goo/*.py')
Out[*]: True
In [*]: pathlib.PurePath('src/goo/scripts/main.py').match('/*.py')
Out[*]: False
Исходя из приведенных примеров, шаблон должен совпадать с путем. Если заданный шаблон является абсолютным, то и путь должен быть абсолютным.
PurePath().with_name() изменяет имя конечного компонента вместе с его суффиксом:
In [*]: p = pathlib.PurePath('/src/goo/scripts/main.py')
p.with_name('app.js')
Out[*]: PurePosixPath('/src/goo/scripts/app.js')
In [*]: p
Out[*]: PurePosixPath('/src/goo/scripts/main.py')
Метод .with_name()
не изменяет имя последнего компонента навсегда. Кроме того, если указанный путь не содержит имени, возникает ошибка, как отмечено в официальной документации.
PurePath().with_stem() изменяет только имя последнего компонента пути:
In [*]: p = pathlib.PurePath('/src/goo/scripts/main.py')
p.with_stem('app.py')
Out[*]: PurePosixPath('/src/goo/scripts/app.py')
In [*]: p
Out[*]: PurePosixPath('/src/goo/scripts/main.py')
Это аналогично методу .with_name()
. Метод .with_stem()
изменяет имя последнего компонента на время. Также, если указанный путь не содержит имени, произойдет ошибка.
PurePath().with_suffix() временно изменяет суффикс или расширение последнего компонента пути:
In [*]: p = pathlib.PurePath('/src/goo/scripts/main.py')
p.with_suffix('.js')
Out[*]: PurePosixPath('/src/goo/scripts/main.js')
Если имя заданного пути не содержит суффикса, метод .with_suffix()
добавляет суффикс за вас:
In [*]: p = pathlib.PurePath('/src/goo/scripts/main')
p.with_suffix('.py')
Out[*]: PurePosixPath('/src/goo/scripts/main.py')
Но если мы не включим суффикс и оставим аргумент пустым ' '
, текущий суффикс будет удален.
In [*]: p = pathlib.PurePath('/src/goo/scripts/main')
p.with_suffix('')
Out[*]: PurePosixPath('/src/goo/scripts/main')
Некоторые методы, такие как .with_stem()
и .is_relative_to()
, были недавно добавлены в Python 3.9 и выше. Поэтому, если вы их вызываете, используя Python 3.8 или ниже, будет выдана ошибка атрибута.
Concrete Path в Python
Concrete Paths позволяет обрабатывать, манипулировать и выполнять операции записи над различными путями файловой системы.
Другими словами, этот тип объекта пути помогает нам создать, скажем, новый файл, новый каталог и выполнить другие операции ввода/вывода, не находясь в данной операционной системе.
Как использовать Concrete path
В Concrete path обрабатываются любые пути файловой системы и выполняются системные вызовы на вашем компьютере. Эти объекты пути являются дочерними путями Pure path и состоят из трех подклассов, как и сами Pure path:
Path() — это дочерний узел PurePath()
, он обеспечивает операции обработки с возможностью выполнения процесса записи.
Когда вы инстанцируете Path()
, он создает два класса для работы с путями Windows и отличных от Windows. Как и PurePath()
, Path()
также создает общий объект пути "agnostic path", независимо от операционной системы, в которой вы работаете.
In [*]: pathlib.Path('setup.py')
Out[*]: PosixPath('setup.py')
Path()
в приведенном выше примере создает PosixPath()
, поскольку мы предполагаем, что работаем на Linux. Но если вы создадите его в Windows, то получите что-то вроде WindowsPath('setup.py')
.
PosixPath() — это дочерний узел Path()
и PurePosixPath()
, реализованный для обработки и управления путями файловой системы, отличной от Windows.
In [*]: pathlib.PosixPath('setup.py')
Out[*]: PosixPath('setup.py')
Вы получите ошибку при инстанцировании PosixPath()
на компьютере с Windows, поскольку нельзя выполнять системные вызовы во время работы в другой операционной системе.
WindowsPath() — это дочерний узел Path()
и PureWindowsPath()
, реализованный для путей файловой системы Windows.
In [*]: pathlib.WindowsPath('setup.py')
Out[*]: WindowsPath('setup.py')
То же самое относится и к WindowsPath()
, поскольку вы работаете в другой операционной системе — поэтому ее инстанцирование приведет к ошибке.
Свойства Concrete path
Поскольку Concrete path является подклассом Pure path, вы можете делать с Concrete path все, что угодно, используя свойства PurePath()
. Это означает, что мы можем использовать, например, свойство .with_suffix
для добавления суффикса к Concrete path:
In [*]: p = pathlib.Path('/src/goo/scripts/main')
p.with_suffix('.py')
Out[*]: PosixPath('/src/goo/scripts/main.py')
Или вы можете проверить, относится ли данный путь к исходному пути с помощью .is_relative_to
:
In [*]: p = pathlib.Path('/src/goo/scripts/main.py')
p.is_relative_to('/src')
Out[*]: True
Всегда помните, что Concrete path наследуют операции обработки от Pure path и добавляют операции записи, которые выполняют системные вызовы и конфигурации ввода/вывода.
Методы Concrete path
Каждый подкласс Path()
предоставляет следующие методы для обработки путей и выполнения системных вызовов:
Path().iterdir() возвращает содержимое каталога. Допустим, у нас есть следующая папка, содержащая следующие файлы:
data
population.json
density.json
temperature.yml
stats.md
details.txt
папка данных
Чтобы вернуть содержимое каталога /data
, вы можете использовать метод .iterdir()
:
In [*]: p = pathlib.Path('/data')
for child in p.iterdir():
print(child)
Out[*]: PosixPath('/data/population.json')
PosixPath('/data/density.json')
PosixPath('/data/temprature.yml')
PosixPath('/data/stats.md')
PosixPath('/data/details.txt')
Метод .itertir()
создает итератор, который случайным образом перечисляет файлы.
Path().exists() проверяет, существует ли файл/каталог в текущем пути. Давайте воспользуемся каталогом из предыдущего примера (наш текущий каталог — /data
):
In [*]: p = pathlib.Path('density.json').exists()
p
Out[*]: True
Метод .exists() возвращает True
, если заданный файл существует в каталоге data
. Метод возвращает False
, если его нет.
In [*]: p = pathlib.Path('aliens.py').exists()
p
Out[*]: False
То же самое относится и к каталогам, метод возвращает True
, если заданный каталог существует, и False
, если каталог отсутствует.
Path().mkdir() создает новый каталог по заданному пути:
In [*]: p = pathlib.Path('data')
directory = pathlib.Path('data/secrets')
directory.exists()
Out[*]: False
In [*]: directory.mkdir(parents = False, exist_ok = False)
directory.exists()
Out[*]: True
Согласно официальной документации, метод .mkdir()
принимает три аргумента. В данный момент мы сосредоточимся только на аргументах parents
и exist_ok
.
По умолчанию оба аргумента имеют значение False
. Аргумент parents выдает ошибку FileNotFound в случае отсутствия родителя, а exist_ok
выдает ошибку FileExists, если данный каталог уже существует.
В приведенном примере вы можете установить аргументы в True
, чтобы игнорировать упомянутые ошибки и обновить каталог.
Мы также можем создать новый файл по указанному пути с помощью метода Path().touch()
:
In [*]: file = pathlib.Path('data/secrets/secret_one.md')
file.exists()
Out[*]: False
In [*]: file.touch(exist_ok = False)
file.exists()
Out[*]: True
Та же логика применима к методу .touch()
. Здесь параметр exist_ok
может быть установлен в True
, чтобы игнорировать ошибку FileExists и обновить файл.
Path().rename() переименовывает файл/каталог по заданному пути. Рассмотрим пример на примере нашего каталога /data
:
In [*]: p = pathlib.Path('density.json')
n = pathlib.Path('density_2100.json')
p.rename(n)
Out[*]: PosixPath('density_2100.json')
Если вы присваиваете методу несуществующий файл, он выдает ошибку FileNotFound. То же самое относится и к каталогам.
Path().read_text() возвращает содержимое файла в формате строки:
In [*]: p = pathlib.Path('info.txt')
p.read_text()
Out[*]: 'some text added'
Также вы можете использовать метод write_text()
для записи содержимого в файл:
In [*]: p = pathlib.Path('file.txt')
p.write_text('we are building an empire')
Out[*]: 'we are building an empire'
Обратите внимание, что метод .write_text()
был добавлен в Python 3.5 и недавно был обновлен в Python 3.10, получив некоторые дополнительные параметры.
Важное замечание
Вы можете спросить себя, зачем использовать пути файловой системы Windows — ведь каждый пакет должен быть совместим и с другими операционными системами, а не только с Windows.
Вы правы, если цель состоит в том, чтобы сделать путь, не зависящий от ОС. Но иногда мы не можем этого сделать из-за некоторых параметров, уникальных для Windows или Posix систем. Вот почему предоставляются данные объекты — чтобы помочь разработчикам справиться с такими вариантами использования.
Некоторые пакеты нацелены на решение проблем, присутствующих только в экосистеме Windows, и Python поддерживает эти юзкейсы в данной библиотеке.
Что дальше?
Надеемся, это руководство помогло вам узнать, как и зачем использовать Pathlib и в чем его польза для обработки и манипулирования путями файловой системы.
Было бы здорово обыграть полученные знания и воплотить их в реальном проекте.
В этой статье я рассказал об основах, необходимых для использования Pathlib в вашем проекте.
В официальной документации описано больше методов и свойств, которые вы можете применить к путям файловой системы: Pathlib - Объектно-ориентированные пути файловой системы
Всех желающих приглашаем на открытое занятие, на котором научимся работать со встроенными модулями. Узнаем про модули (os, pathlib, functools). Регистрация открыта по ссылке.