В каждой операционной системе существуют свои правила построения путей к файлам. Например, в 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.

Классы PurePath()
Классы PurePath()

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.py и предоставляет только имя файла.

Методы 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). Регистрация открыта по ссылке.

Комментарии (1)


  1. zoktog
    22.11.2022 17:31

    мне кажется, нужно было ещё в самое начало добавить: "почему стоит использовать pathlib, а не os.path"