Манипуляции с путями к файлам и каталогам в Python

PythonBeginner
Практиковаться сейчас

Введение

В этой лабораторной работе вы научитесь работать с путями к файлам и каталогам в Python, используя современный модуль pathlib. Хотя старый модуль os.path все еще широко распространен, pathlib предлагает объектно-ориентированный подход, который делает манипуляции с путями более интуитивно понятными и читаемыми. Вы научитесь создавать, проверять, валидировать и изменять пути, файлы и каталоги таким образом, чтобы это работало на разных операционных системах, таких как Windows, macOS и Linux.

К концу этой лабораторной работы вы будете уверенно владеть следующими навыками:

  • Правильное объединение компонентов пути.
  • Создание и управление каталогами.
  • Различение абсолютных и относительных путей.
  • Проверка существования и типа путей.
  • Выполнение общих файловых операций, таких как копирование, перемещение и удаление.
  • Обход древовидных структур каталогов для поиска файлов и папок.

Базовые операции с путями

На этом этапе вы изучите основы манипулирования путями с использованием модуля pathlib. Это включает объединение компонентов пути, получение текущего рабочего каталога и создание новых каталогов.

Модуль pathlib предоставляет элегантный, кроссплатформенный способ работы с путями файловой системы. В Windows пути используют обратные слеши (\), тогда как в Unix-подобных системах (Linux, macOS) используются прямые слеши (/). Использование pathlib гарантирует, что ваш код будет работать на любой платформе без ручной настройки.

Примечание: Если вы не знакомы с использованием WebIDE (похожего на VS Code), пожалуйста, изучите его сначала в этой лабораторной работе.

Сначала посмотрим, как объединять компоненты пути. Оператор / является интуитивно понятным сокращением для метода joinpath().

  1. Откройте файл main.py в проводнике файлов слева.
  2. Замените содержимое main.py следующим кодом для объединения компонентов пути и вывода результата.
from pathlib import Path

## Объединение компонентов пути с использованием оператора /
my_path = Path('usr') / 'bin' / 'spam'
print(my_path)
  1. Запустите скрипт из терминала:
python3 main.py

Вы увидите следующий вывод, который демонстрирует, как pathlib корректно формирует путь в стиле Unix:

usr/bin/spam

Далее, давайте получим текущий рабочий каталог с помощью Path.cwd().

  1. Замените содержимое main.py следующим кодом (это заменит предыдущий код):
from pathlib import Path

## Получение текущего рабочего каталога
cwd = Path.cwd()
print(cwd)
  1. Снова запустите скрипт:
python3 main.py

Вывод покажет текущий рабочий каталог, который в данной среде равен /home/labex/project:

/home/labex/project

Наконец, давайте создадим новый каталог. Возникнет FileNotFoundError, если вы попытаетесь создать каталог, родительский каталог которого не существует. Чтобы избежать этого, используйте mkdir(parents=True), который создает все необходимые родительские каталоги.

  1. Замените содержимое main.py следующим кодом для создания вложенной структуры каталогов:
from pathlib import Path

## Получение текущего рабочего каталога
cwd = Path.cwd()

## Определение пути для новых вложенных каталогов
new_dirs = cwd / 'delicious' / 'walnut' / 'waffles'

## Создание каталогов, включая любые отсутствующие родительские
new_dirs.mkdir(parents=True, exist_ok=True)

print(f"Successfully created: {new_dirs}")

Аргумент exist_ok=True предотвращает ошибку, если каталог уже существует. Это означает, что вы можете запускать скрипт несколько раз без ошибок.

  1. Запустите скрипт:
python3 main.py

Скрипт создаст вложенные каталоги и выведет подтверждающее сообщение:

Successfully created: /home/labex/project/delicious/walnut/waffles

Вы можете проверить создание каталога, выполнив в терминале команду ls -R delicious.

Абсолютные и относительные пути

На этом этапе вы научитесь различать абсолютные и относительные пути и работать с ними.

  • Абсолютный путь (absolute path) начинается от корня файловой системы (например, / в Linux или C:\ в Windows).
  • Относительный путь (relative path) определяется относительно текущего рабочего каталога.

pathlib предоставляет методы для проверки того, является ли путь абсолютным, для приведения пути к его абсолютному виду и для нахождения относительного пути от отправной точки.

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

  1. Замените содержимое main.py следующим кодом:
from pathlib import Path

## Проверка, являются ли пути абсолютными
print(f"Path('/') is absolute: {Path('/').is_absolute()}")
print(f"Path('..') is absolute: {Path('..').is_absolute()}")
  1. Запустите скрипт:
python3 main.py

Вывод подтверждает, что / является абсолютным путем, в то время как .. (родительский каталог) — относительным.

Path('/') is absolute: True
Path('..') is absolute: False

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

  1. Замените содержимое main.py следующим кодом:
from pathlib import Path

## Приведение относительного пути к его абсолютному виду
## '..' ссылается на родительский каталог текущего рабочего каталога
resolved_path = Path('..').resolve()

print(f"Current working directory: {Path.cwd()}")
print(f"Resolved path of '..': {resolved_path}")
  1. Выполните скрипт:
python3 main.py

Вывод покажет текущий каталог и приведенный к абсолютному виду путь к его родителю.

Current working directory: /home/labex/project
Resolved path of '..': /home/labex

Наконец, вы можете вычислить относительный путь от одного пути к другому, используя relative_to().

  1. Замените содержимое main.py следующим кодом для вычисления относительного пути:
from pathlib import Path

## Получение относительного пути от отправной точки
## Это показывает путь к '/etc/passwd' относительно корневого каталога '/'
relative_path = Path('/etc/passwd').relative_to('/')
print(f"Path to '/etc/passwd' relative to '/': {relative_path}")
  1. Запустите скрипт:
python3 main.py

Вывод показывает путь относительно корневого каталога:

Path to '/etc/passwd' relative to '/': etc/passwd

Проверка и инспекция путей

На этом этапе вы узнаете, как проверять свойства пути, например, существует ли он и является ли он файлом или каталогом. Вы также узнаете, как извлекать метаданные файла, такие как его размер.

Распространенная задача — убедиться, что файл или каталог существует, прежде чем пытаться получить к нему доступ. Метод exists() идеально подходит для этого.

  1. Откройте main.py и замените его содержимое следующим кодом. Он проверит существование текущего каталога (.), файла (setup.py) и несуществующего файла.
from pathlib import Path

## Проверка существования путей
print(f"Path('.') exists: {Path('.').exists()}")
print(f"Path('setup.py') exists: {Path('setup.py').exists()}")
print(f"Path('nonexistentfile') exists: {Path('nonexistentfile').exists()}")
  1. Запустите скрипт:
python3 main.py

Вывод подтвердит, какие пути существуют:

Path('.') exists: True
Path('setup.py') exists: True
Path('nonexistentfile') exists: False

Вы также можете быть более конкретными и проверить, указывает ли путь на файл с помощью is_file(), или на каталог с помощью is_dir().

  1. Замените содержимое main.py следующим кодом для выполнения этих проверок:
from pathlib import Path

## Проверка, является ли путь файлом
print(f"Path('setup.py').is_file(): {Path('setup.py').is_file()}")
print(f"Path('/home').is_file(): {Path('/home').is_file()}")

## Проверка, является ли путь каталогом
print(f"Path('/').is_dir(): {Path('/').is_dir()}")
print(f"Path('setup.py').is_dir(): {Path('setup.py').is_dir()}")
  1. Выполните скрипт:
python3 main.py

Вывод корректно определит тип каждого пути:

Path('setup.py').is_file(): True
Path('/home').is_file(): False
Path('/').is_dir(): True
Path('setup.py').is_dir(): False

Наконец, чтобы получить подробную информацию о файле, например, его размер, вы можете использовать метод stat(). Он возвращает объект с различными атрибутами, включая st_size для размера файла в байтах.

  1. Замените содержимое main.py следующим кодом, чтобы получить размер системного файла:
from pathlib import Path
import os

## Определение пути к файлу
file_path = Path('/bin/python3')

## Получение статистики файла
stat_info = file_path.stat()

## Вывод размера в байтах
print(f"File: {file_path}")
print(f"Size: {stat_info.st_size} bytes")
  1. Запустите скрипт:
python3 main.py

Вывод отобразит размер исполняемого файла Python в байтах. Точный размер может варьироваться.

File: /bin/python3
Size: 10024 bytes

Управление файлами и папками

На этом шаге вы научитесь выполнять общие операции с файловой системой, такие как копирование, перемещение, переименование и удаление файлов и папок, используя модуль shutil в сочетании с pathlib.

Модуль shutil предоставляет функции высокого уровня для файловых операций. Начнем с копирования файлов. shutil.copy() копирует один файл в новое местоположение.

  1. Откройте файл manage_files.py в проводнике файлов слева.
  2. Замените содержимое manage_files.py следующим кодом. Это скопирует spam.txt в каталог /tmp/delicious.
import shutil
from pathlib import Path

## Define source and destination
source_file = Path('/tmp/spam.txt')
destination_dir = Path('/tmp/delicious')

## Copy the file
shutil.copy(source_file, destination_dir)

print(f"Copied '{source_file}' to '{destination_dir}'")

## You can also rename the file while copying
shutil.copy('/tmp/eggs.txt', '/tmp/delicious/eggs2.txt')
print("Copied and renamed 'eggs.txt' to 'eggs2.txt'")
  1. Запустите скрипт:
python3 manage_files.py

Чтобы скопировать все дерево каталогов, используйте shutil.copytree().

  1. Добавьте следующие строки в конец manage_files.py (добавьте их после существующего кода):
## Copy an entire directory tree
shutil.copytree('/tmp/bacon', '/tmp/bacon_backup')
print("Copied directory '/tmp/bacon' to '/tmp/bacon_backup'")

Примечание: Если вы запустите этот скрипт несколько раз, shutil.copytree() завершится ошибкой, поскольку целевой каталог уже существует. Это ожидаемое поведение. Пока просто запустите его один раз.

  1. Запустите скрипт еще раз, чтобы увидеть результат.

Далее рассмотрим перемещение и переименование файлов с помощью shutil.move(). Эта функция может либо переместить файл в другой каталог, либо переименовать его.

  1. Замените все содержимое manage_files.py следующим кодом, чтобы переместить bacon.txt:
import shutil
from pathlib import Path

## Create the destination directory if it doesn't exist
Path('/tmp/eggs').mkdir(parents=True, exist_ok=True)

## Move a file to a directory (only if source exists)
bacon_source = Path('/tmp/bacon.txt')
if bacon_source.exists():
    shutil.move('/tmp/bacon.txt', '/tmp/eggs')
    print("Moved '/tmp/bacon.txt' to '/tmp/eggs'")
else:
    print("'/tmp/bacon.txt' not found (may have been moved already)")

## Move and rename a file
## First, let's recreate the file to move
Path('/tmp/bacon.txt').touch()
shutil.move('/tmp/bacon.txt', '/tmp/eggs/new_bacon.txt')
print("Moved and renamed '/tmp/bacon.txt' to '/tmp/eggs/new_bacon.txt'")

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

  1. Запустите скрипт.

Наконец, рассмотрим удаление файлов и папок.

  • Path.unlink() удаляет один файл.
  • Path.rmdir() удаляет пустой каталог.
  • shutil.rmtree() удаляет каталог и все его содержимое.
  1. Замените все содержимое manage_files.py следующим:
from pathlib import Path
import shutil

## Create a file to delete
file_to_delete = Path('/tmp/deleteme.txt')
file_to_delete.touch()
print(f"Created '{file_to_delete}'")

## Delete the file
file_to_delete.unlink()
print(f"Deleted '{file_to_delete}'")

## Create a directory to delete
dir_to_delete = Path('/tmp/temp_dir')
dir_to_delete.mkdir(exist_ok=True)
## rmdir() only works on empty directories
dir_to_delete.rmdir()
print(f"Deleted empty directory '{dir_to_delete}'")

## Use shutil.rmtree() to delete a directory and its contents
## Note: This will fail if the directory doesn't exist, which is fine for this example
if Path('/tmp/bacon_backup').exists():
    shutil.rmtree('/tmp/bacon_backup')
    print("Deleted directory '/tmp/bacon_backup' and all its contents")
else:
    print("Directory '/tmp/bacon_backup' does not exist (may have been deleted already)")

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

  1. Запустите скрипт для выполнения операций удаления.

Обход каталогов

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

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

  1. Сначала настроим пример структуры каталогов. Выполните эту команду в терминале:
mkdir -p ~/project/data/level1 && touch ~/project/data/file0.txt ~/project/data/level1/file1.txt
  1. Теперь откройте main.py и замените его содержимое следующим кодом для вывода содержимого каталога /usr/bin.
from pathlib import Path

## Вывод содержимого каталога
print("Contents of '/usr/bin':")
count = 0
for f in Path('/usr/bin').iterdir():
    if count < 5: ## Выводим первые 5 элементов, чтобы вывод был коротким
        print(f)
        count += 1
    else:
        break
  1. Запустите скрипт:
python3 main.py

Вывод покажет первые несколько файлов и каталогов внутри /usr/bin. Точный список может отличаться.

Contents of '/usr/bin':
/usr/bin/[
/usr/bin/addpart
/usr/bin/apt
/usr/bin/apt-cache
/usr/bin/apt-cdrom

Вы можете скомбинировать iterdir() с stat(), чтобы вычислить общий размер всех файлов в каталоге.

  1. Замените содержимое main.py следующим кодом. Обратите внимание, что этот код суммирует размеры только прямых потомков и не рекурсивно спускается в подкаталоги.
from pathlib import Path

total_size = 0
directory = Path('/usr/bin')

for sub_path in directory.iterdir():
    if sub_path.is_file(): ## Убеждаемся, что добавляем только размер файлов
        total_size += sub_path.stat().st_size

print(f"Total size of files in '{directory}': {total_size} bytes")
  1. Выполните скрипт. Он выведет общий размер всех файлов, находящихся непосредственно в /usr/bin.

Для более сложных задач вам может потребоваться "пройти" (walk) по всему дереву каталогов, посещая все подкаталоги и файлы. Метод rglob() идеально подходит для этого. Он рекурсивно находит все пути, соответствующие заданному шаблону. Использование * в качестве шаблона соответствует всему.

  1. Сначала создайте более сложную структуру каталогов для этого примера. Выполните это в терминале:
mkdir -p /tmp/delicious/cats /tmp/delicious/walnut/waffles
touch /tmp/delicious/spam.txt /tmp/delicious/cats/catnames.txt /tmp/delicious/walnut/waffles/butter.txt

Примечание: Вы можете безопасно запускать эти команды несколько раз. Команда mkdir -p не вызовет ошибку, если каталоги уже существуют, а touch обновит временные метки файлов, если файлы уже существуют.

  1. Замените содержимое main.py следующим кодом для обхода каталога /tmp/delicious:
from pathlib import Path

p = Path('/tmp/delicious')
print(f"Walking directory tree for: {p}")

for i in p.rglob('*'):
    print(i)
  1. Запустите скрипт:
python3 main.py

Вывод будет содержать список каждого файла и папки внутри /tmp/delicious и его подкаталогов, демонстрируя, как rglob() обходит все дерево.

Walking directory tree for: /tmp/delicious
/tmp/delicious/spam.txt
/tmp/delicious/eggs2.txt
/tmp/delicious/cats
/tmp/delicious/walnut
/tmp/delicious/cats/catnames.txt
/tmp/delicious/walnut/waffles
/tmp/delicious/walnut/waffles/butter.txt

Резюме

Поздравляем с завершением лабораторной работы! Вы научились эффективно управлять путями к файлам и каталогам в Python, используя современный модуль pathlib.

В ходе этой лабораторной работы вы практиковались в следующем:

  • Создание и объединение путей кроссплатформенным способом с использованием объектов Path и оператора /.
  • Получение текущего рабочего каталога и создание вложенных каталогов с помощью mkdir(parents=True).
  • Различие между абсолютными и относительными путями и преобразование между ними с использованием is_absolute(), resolve() и relative_to().
  • Проверка путей с помощью exists(), is_file() и is_dir(), а также инспекция метаданных файлов с помощью stat().
  • Выполнение основных задач по управлению файлами, таких как копирование, перемещение и удаление, с помощью модуля shutil и методов pathlib.
  • Обход структур каталогов для вывода содержимого с помощью iterdir() и рекурсивного поиска элементов с помощью rglob().

Эти навыки являются основополагающими для написания надежных и поддерживаемых Python-приложений, которые взаимодействуют с файловой системой.