Рубрики
Без рубрики

pathlib – Пути файловой системы как объекты

Автор оригинала: Doug Hellmann.

Цель:

Разбирать, строить, тестировать и иным образом работать с именами файлов и пути с использованием объектно-ориентированного API вместо низкоуровневого строковые операции.

Представления пути

pathlib включает классы для управления путями файловой системы, отформатированные с использованием либо стандарта POSIX, либо синтаксиса Microsoft Windows. Он включает в себя так называемые «чистые» классы, которые работают со строками, но не взаимодействуют с реальной файловой системой, и «конкретные» классы, которые расширяют API для включения операций, отражающих или изменяющих данные в локальной файловой системе.

Чистые классы PurePosixPath и PureWindowsPath могут быть созданы и использоваться в любой операционной системе, поскольку они работают только с именами. Чтобы создать экземпляр правильного класса для работы с реальной файловой системой, используйте Path , чтобы получить либо PosixPath , либо WindowsPath , в зависимости от платформы.

Строительные пути

Чтобы создать новый путь, укажите строку в качестве первого аргумента. Это значение имени представляет собой строковое представление объекта пути. Чтобы создать новый путь, ссылающийся на значение относительно существующего пути, используйте оператор / для расширения пути. Аргументом оператора может быть строка или другой объект пути.

pathlib_operator.py

import pathlib

usr  pathlib.PurePosixPath('/usr')
print(usr)

usr_local  usr / 'local'
print(usr_local)

usr_share  usr / pathlib.PurePosixPath('share')
print(usr_share)

root  usr / '..'
print(root)

etc  root / '/etc/'
print(etc)

Как показывает значение для root в выходных данных примера, оператор объединяет значения пути в том виде, в каком они заданы, и не нормализует результат, если он содержит ссылку на родительский каталог ".." . Однако, если сегмент начинается с разделителя пути, он интерпретируется как новая «корневая» ссылка так же, как os.path.join () . Лишние разделители пути удаляются из середины значения пути, как в примере etc здесь.

$ python3 pathlib_operator.py

/usr
/usr/local
/usr/share
/usr/..
/etc

Классы конкретных путей включают метод resolve () для нормализации пути путем просмотра файловой системы на предмет каталогов и символических ссылок и создания абсолютного пути, на который ссылается имя.

pathlib_resolve.py

import pathlib

usr_local  pathlib.Path('/usr/local')
share  usr_local / '..' / 'share'
print(share.resolve())

Здесь относительный путь преобразуется в абсолютный путь к /usr/share . Если входной путь включает символические ссылки, они также расширяются, чтобы разрешенный путь мог ссылаться непосредственно на цель.

$ python3 pathlib_resolve.py

/usr/share

Чтобы построить пути, когда сегменты заранее не известны, используйте joinpath () , передавая каждый сегмент пути как отдельный аргумент.

pathlib_joinpath.py

import pathlib

root  pathlib.PurePosixPath('/')
subdirs  ['usr', 'local']
usr_local  root.joinpath(*subdirs)
print(usr_local)

Как и в случае с оператором /, вызов joinpath () создает новый экземпляр.

$ python3 pathlib_joinpath.py

/usr/local

Учитывая существующий объект пути, легко создать новый с небольшими отличиями, такими как ссылка на другой файл в том же каталоге. Используйте with_name () , чтобы создать новый путь, который заменяет часть имени пути другим именем файла. Используйте with_suffix () , чтобы создать новый путь, который заменяет расширение имени файла другим значением.

pathlib_from_existing.py

import pathlib

ind  pathlib.PurePosixPath('source/pathlib/index.rst')
print(ind)

py  ind.with_name('pathlib_from_existing.py')
print(py)

pyc  py.with_suffix('.pyc')
print(pyc)

Оба метода возвращают новые объекты, а исходный остается без изменений.

$ python3 pathlib_from_existing.py

source/pathlib/index.rst
source/pathlib/pathlib_from_existing.py
source/pathlib/pathlib_from_existing.pyc

Парсинг путей

У объектов пути есть методы и свойства для извлечения частичных значений из имени. Например, свойство parts создает последовательность сегментов пути, анализируемых на основе значения разделителя пути.

pathlib_parts.py

import pathlib

p  pathlib.PurePosixPath('/usr/local')
print(p.parts)

Последовательность представляет собой кортеж, отражающий неизменность экземпляра пути.

$ python3 pathlib_parts.py

('/', 'usr', 'local')

Есть два способа перейти «вверх» по иерархии файловой системы от заданного объекта пути. Свойство parent относится к новому экземпляру пути для каталога, содержащего путь, значение, возвращаемое os.path.dirname () . Свойство parent – это итерация, которая создает ссылки на родительские каталоги, непрерывно поднимаясь «вверх» по иерархии путей до достижения корня.

pathlib_parents.py

import pathlib

p  pathlib.PurePosixPath('/usr/local/lib')

print('parent: {}'.format(p.parent))

print('\nhierarchy:')
for up in p.parents:
    print(up)

В примере выполняется итерация по свойству parent и выводятся значения элементов.

$ python3 pathlib_parents.py

parent: /usr/local

hierarchy:
/usr/local
/usr
/

К другим частям пути можно получить доступ через свойства объекта пути. Свойство name содержит последнюю часть пути после последнего разделителя пути (то же значение, что и os.path.basename () ). Свойство суффикс содержит значение после разделителя расширения, а свойство stem содержит часть имени перед суффиксом.

pathlib_name.py

import pathlib

p  pathlib.PurePosixPath('./source/pathlib/pathlib_name.py')
print('path  : {}'.format(p))
print('name  : {}'.format(p.name))
print('suffix: {}'.format(p.suffix))
print('stem  : {}'.format(p.stem))

Хотя значения suffix и stem аналогичны значениям, создаваемым os.path.splitext () , значения основаны только на значении name , а не полный путь.

$ python3 pathlib_name.py

path  : source/pathlib/pathlib_name.py
name  : pathlib_name.py
suffix: .py
stem  : pathlib_name

Создание бетонных дорожек

Экземпляры конкретного класса Path могут быть созданы из строковых аргументов, относящихся к имени (или потенциальному имени) файла, каталога или символической ссылки в файловой системе. Класс также предоставляет несколько удобных методов для создания экземпляров с использованием часто используемых мест, которые меняются, таких как текущий рабочий каталог и домашний каталог пользователя.

pathlib_convenience.py

import pathlib

home  pathlib.Path.home()
print('home: ', home)

cwd  pathlib.Path.cwd()
print('cwd : ', cwd)

Оба метода создают экземпляры Path , предварительно заполненные абсолютной ссылкой на файловую систему.

$ python3 pathlib_convenience.py

home:  /Users/dhellmann
cwd :  /Users/dhellmann/PyMOTW

Содержание каталога

Есть три метода доступа к спискам каталогов, чтобы узнать имена файлов, доступных в файловой системе. iterdir () – это генератор, создающий новый экземпляр Path для каждого элемента в содержащем каталоге.

pathlib_iterdir.py

import pathlib

p  pathlib.Path('.')

for f in p.iterdir():
    print(f)

Если Path не ссылается на каталог, iterdir () вызывает NotADirectoryError .

$ python3 pathlib_iterdir.py

example_link
index.rst
pathlib_chmod.py
pathlib_convenience.py
pathlib_from_existing.py
pathlib_glob.py
pathlib_iterdir.py
pathlib_joinpath.py
pathlib_mkdir.py
pathlib_name.py
pathlib_operator.py
pathlib_ownership.py
pathlib_parents.py
pathlib_parts.py
pathlib_read_write.py
pathlib_resolve.py
pathlib_rglob.py
pathlib_rmdir.py
pathlib_stat.py
pathlib_symlink_to.py
pathlib_touch.py
pathlib_types.py
pathlib_unlink.py

Используйте glob () , чтобы найти только файлы, соответствующие шаблону.

pathlib_glob.py

import pathlib

p  pathlib.Path('..')

for f in p.glob('*.rst'):
    print(f)

В этом примере показаны все входные файлы reStructuredText в родительском каталоге сценария.

$ python3 pathlib_glob.py

../about.rst
../algorithm_tools.rst
../book.rst
../compression.rst
../concurrency.rst
../cryptographic.rst
../data_structures.rst
../dates.rst
../dev_tools.rst
../email.rst
../file_access.rst
../frameworks.rst
../i18n.rst
../importing.rst
../index.rst
../internet_protocols.rst
../language.rst
../networking.rst
../numeric.rst
../persistence.rst
../porting_notes.rst
../runtime_services.rst
../text.rst
../third_party.rst
../unix.rst

Процессор glob поддерживает рекурсивное сканирование с использованием префикса шаблона ** или путем вызова rglob () вместо glob () .

pathlib_rglob.py

import pathlib

p  pathlib.Path('..')

for f in p.rglob('pathlib_*.py'):
    print(f)

Поскольку этот пример начинается с родительского каталога, необходим рекурсивный поиск, чтобы найти файлы примеров, соответствующие pathlib _ *. Py .

$ python3 pathlib_rglob.py

../pathlib/pathlib_chmod.py
../pathlib/pathlib_convenience.py
../pathlib/pathlib_from_existing.py
../pathlib/pathlib_glob.py
../pathlib/pathlib_iterdir.py
../pathlib/pathlib_joinpath.py
../pathlib/pathlib_mkdir.py
../pathlib/pathlib_name.py
../pathlib/pathlib_operator.py
../pathlib/pathlib_ownership.py
../pathlib/pathlib_parents.py
../pathlib/pathlib_parts.py
../pathlib/pathlib_read_write.py
../pathlib/pathlib_resolve.py
../pathlib/pathlib_rglob.py
../pathlib/pathlib_rmdir.py
../pathlib/pathlib_stat.py
../pathlib/pathlib_symlink_to.py
../pathlib/pathlib_touch.py
../pathlib/pathlib_types.py
../pathlib/pathlib_unlink.py

Чтение и запись файлов

Каждый экземпляр Path включает методы для работы с содержимым файла, на который он ссылается. Для немедленного получения содержимого используйте read_bytes () или read_text () . Для записи в файл используйте write_bytes () или write_text () . Используйте метод open () , чтобы открыть файл и сохранить дескриптор файла, вместо передачи имени встроенной функции open () .

pathlib_read_write.py

import pathlib

f  pathlib.Path('example.txt')

f.write_bytes('This is the content'.encode('utf-8'))

with f.open('r', encoding'utf-8') as handle:
    print('read from open(): {!r}'.format(handle.read()))

print('read_text(): {!r}'.format(f.read_text('utf-8')))

Удобные методы выполняют некоторую проверку типов перед открытием файла и записью в него, но в остальном они эквивалентны выполнению операции напрямую.

$ python3 pathlib_read_write.py

read from open(): 'This is the content'
read_text(): 'This is the content'

Управление каталогами и символическими ссылками

Пути, представляющие каталоги или символические ссылки, которые не существуют, могут использоваться для создания связанных записей файловой системы.

pathlib_mkdir.py

import pathlib

p  pathlib.Path('example_dir')

print('Creating {}'.format(p))
p.mkdir()

Если путь уже существует, mkdir () вызывает FileExistsError .

$ python3 pathlib_mkdir.py

Creating example_dir

$ python3 pathlib_mkdir.py

Creating example_dir
Traceback (most recent call last):
  File "pathlib_mkdir.py", line 16, in 
    p.mkdir()
  File ".../lib/python3.6/pathlib.py", line 1226, in mkdir
    self._accessor.mkdir(self, mode)
  File ".../lib/python3.6/pathlib.py", line 387, in wrapped
    return strfunc(str(pathobj), *args)
FileExistsError: [Errno 17] File exists: 'example_dir'

Используйте symlink_to () , чтобы создать символическую ссылку. Ссылка будет названа на основе значения пути и будет ссылаться на имя, указанное в качестве аргумента для symlink_to () .

pathlib_symlink_to.py

import pathlib

p  pathlib.Path('example_link')

p.symlink_to('index.rst')

print(p)
print(p.resolve().name)

В этом примере создается символическая ссылка, затем используется resolve () для чтения ссылки, чтобы найти то, на что она указывает, и распечатать имя.

$ python3 pathlib_symlink_to.py

example_link
index.rst

Типы файлов

Экземпляр Path включает несколько методов для проверки типа файла, на который ссылается путь. В этом примере создается несколько файлов разных типов и проверяется их, а также несколько других файлов для конкретных устройств, доступных в локальной операционной системе.

pathlib_types.py

import itertools
import os
import pathlib

root  pathlib.Path('test_files')

# Clean up from previous runs.
if root.exists():
    for f in root.iterdir():
        f.unlink()
else:
    root.mkdir()

# Create test files
(root / 'file').write_text(
    'This is a regular file', encoding'utf-8')
(root / 'symlink').symlink_to('file')
os.mkfifo(str(root / 'fifo'))

# Check the file types
to_scan  itertools.chain(
    root.iterdir(),
    [pathlib.Path('/dev/disk0'),
     pathlib.Path('/dev/console')],
)
hfmt  '{:18s}' + ('  {:>5}' * 6)
print(hfmt.format('Name', 'File', 'Dir', 'Link', 'FIFO', 'Block',
                  'Character'))
print()

fmt  '{:20s}  ' + ('{!r:>5}  ' * 6)
for f in to_scan:
    print(fmt.format(
        str(f),
        f.is_file(),
        f.is_dir(),
        f.is_symlink(),
        f.is_fifo(),
        f.is_block_device(),
        f.is_char_device(),
    ))

Каждый из методов, is_dir () , is_file () , is_symlink () , is_socket () , < code> is_fifo () , is_block_device () и is_char_device () не принимает аргументов.

$ python3 pathlib_types.py

Name                 File    Dir   Link   FIFO  Block  Character

test_files/fifo       False  False  False   True  False  False
test_files/file        True  False  False  False  False  False
test_files/symlink     True  False   True  False  False  False
/dev/disk0            False  False  False  False   True  False
/dev/console          False  False  False  False  False   True

Свойства файла

Подробную информацию о файле можно получить с помощью методов stat () или lstat () (для проверки статуса чего-то, что может быть символической ссылкой). Эти методы дают те же результаты, что и os.stat () и os.lstat () .

pathlib_stat.py

import pathlib
import sys
import time

if len(sys.argv)  1:
    filename  __file__
else:
    filename  sys.argv[1]

p  pathlib.Path(filename)
stat_info  p.stat()

print('{}:'.format(filename))
print('  Size:', stat_info.st_size)
print('  Permissions:', oct(stat_info.st_mode))
print('  Owner:', stat_info.st_uid)
print('  Device:', stat_info.st_dev)
print('  Created      :', time.ctime(stat_info.st_ctime))
print('  Last modified:', time.ctime(stat_info.st_mtime))
print('  Last accessed:', time.ctime(stat_info.st_atime))

Результат будет зависеть от того, как был установлен пример кода. Попробуйте передать в pathlib_stat.py другие имена файлов в командной строке.

$ python3 pathlib_stat.py

pathlib_stat.py:
  Size: 607
  Permissions: 0o100644
  Owner: 527
  Device: 16777220
  Created      : Thu Dec 29 12:38:23 2016
  Last modified: Thu Dec 29 12:38:23 2016
  Last accessed: Sun Mar 18 16:21:41 2018

$ python3 pathlib_stat.py index.rst

index.rst:
  Size: 19569
  Permissions: 0o100644
  Owner: 527
  Device: 16777220
  Created      : Sun Mar 18 16:11:31 2018
  Last modified: Sun Mar 18 16:11:31 2018
  Last accessed: Sun Mar 18 16:21:40 2018

Для упрощения доступа к информации о владельце файла используйте owner () и group () .

pathlib_ownership.py

import pathlib

p  pathlib.Path(__file__)

print('{} is owned by {}/{}'.format(p, p.owner(), p.group()))

Хотя stat () возвращает числовые значения идентификатора системы, эти методы ищут имя, связанное с идентификаторами.

$ python3 pathlib_ownership.py

pathlib_ownership.py is owned by dhellmann/dhellmann

Метод touch () работает так же, как команда Unix touch для создания файла или обновления времени модификации и разрешений существующего файла.

pathlib_touch.py

import pathlib
import time

p  pathlib.Path('touched')
if p.exists():
    print('already exists')
else:
    print('creating new')

p.touch()
start  p.stat()

time.sleep(1)

p.touch()
end  p.stat()

print('Start:', time.ctime(start.st_mtime))
print('End  :', time.ctime(end.st_mtime))

Выполнение этого примера более одного раза обновляет существующий файл при последующих запусках.

$ python3 pathlib_touch.py

creating new
Start: Sun Mar 18 16:21:41 2018
End  : Sun Mar 18 16:21:42 2018

$ python3 pathlib_touch.py

already exists
Start: Sun Mar 18 16:21:42 2018
End  : Sun Mar 18 16:21:43 2018

Разрешения

В Unix-подобных системах права доступа к файлам можно изменить с помощью chmod () , передав режим как целое число. Значения режима могут быть созданы с использованием констант, определенных в модуле stat . В этом примере переключается бит разрешения пользователя на выполнение.

pathlib_chmod.py

import os
import pathlib
import stat

# Create a fresh test file.
f  pathlib.Path('pathlib_chmod_example.txt')
if f.exists():
    f.unlink()
f.write_text('contents')

# Determine what permissions are already set using stat.
existing_permissions  stat.S_IMODE(f.stat().st_mode)
print('Before: {:o}'.format(existing_permissions))

# Decide which way to toggle them.
if not (existing_permissions & os.X_OK):
    print('Adding execute permission')
    new_permissions  existing_permissions | stat.S_IXUSR
else:
    print('Removing execute permission')
    # use xor to remove the user execute permission
    new_permissions  existing_permissions ^ stat.S_IXUSR

# Make the change and show the new value.
f.chmod(new_permissions)
after_permissions  stat.S_IMODE(f.stat().st_mode)
print('After: {:o}'.format(after_permissions))

Сценарий предполагает, что у него есть разрешения, необходимые для изменения режима файла при запуске.

$ python3 pathlib_chmod.py

Before: 644
Adding execute permission
After: 744

Удаление

В зависимости от типа есть два метода удаления файлов из файловой системы. Чтобы удалить пустой каталог, используйте rmdir () .

pathlib_rmdir.py

import pathlib

p  pathlib.Path('example_dir')

print('Removing {}'.format(p))
p.rmdir()

Исключение FileNotFoundError возникает, если пост-условия уже выполнены, а каталог не существует. Также ошибкой является попытка удалить каталог, который не пуст.

$ python3 pathlib_rmdir.py

Removing example_dir

$ python3 pathlib_rmdir.py

Removing example_dir
Traceback (most recent call last):
  File "pathlib_rmdir.py", line 16, in 
    p.rmdir()
  File ".../lib/python3.6/pathlib.py", line 1270, in rmdir
    self._accessor.rmdir(self)
  File ".../lib/python3.6/pathlib.py", line 387, in wrapped
    return strfunc(str(pathobj), *args)
FileNotFoundError: [Errno 2] No such file or directory:
'example_dir'

Для файлов, символических ссылок и большинства других типов путей используйте unlink () .

pathlib_unlink.py

import pathlib

p  pathlib.Path('touched')

p.touch()

print('exists before removing:', p.exists())

p.unlink()

print('exists after removing:', p.exists())

У пользователя должно быть разрешение на удаление файла, символической ссылки, сокета или другого объекта файловой системы.

$ python3 pathlib_unlink.py

exists before removing: True
exists after removing: False

Смотрите также