Python · Синтаксис · Промежуточный

pathlib

10 задач

Научитесь работать с путями файловой системы с помощью современного модуля pathlib в Python — чище и мощнее, чем os.path.

Path-объекты и навигация

#
`pathlib` предоставляет класс `Path` — объектно-ориентированный способ работы с путями файловой системы. Он заменяет строковые манипуляции `os.path` чистым, читабельным доступом к атрибутам. ```python from pathlib import Path ``` ## Создание Path-объектов ```python p = Path("/home/user/documents/report.txt") p = Path(".") # текущая директория p = Path.home() # домашняя директория: /home/user p = Path.cwd() # текущая рабочая директория # Пути Windows тоже работают: p = Path("C:/Users/Alice/Desktop/file.txt") ``` ## Атрибуты Path ```python p = Path("/home/user/projects/script.py") print(p.name) # "script.py" — имя файла с расширением print(p.stem) # "script" — имя файла без расширения print(p.suffix) # ".py" — расширение с точкой print(p.suffixes) # [".py"] — все расширения (для ".tar.gz" → [".tar", ".gz"]) print(p.parent) # /home/user/projects — родительская директория print(p.parts) # ("/", "home", "user", "projects", "script.py") print(p.root) # "/" ``` ## Объединение путей Используйте оператор `/` — намного чище чем `os.path.join`: ```python base = Path("/home/user") config = base / "config" / "settings.json" print(config) # /home/user/config/settings.json # Справа можно передавать строки reports = base / "reports" report_file = reports / "2024" / "q1.csv" ``` ## Проверка существования ```python p = Path("/home/user/data.txt") print(p.exists()) # True/False — файл или директория существует print(p.is_file()) # True если существует и является обычным файлом print(p.is_dir()) # True если существует и является директорией print(p.is_symlink()) # True если является символической ссылкой ``` ## Конвертация и сравнение ```python p = Path("/home/user/file.txt") # Преобразование в строку str(p) # "/home/user/file.txt" p.as_posix() # "/home/user/file.txt" — всегда косые черты # Абсолютный путь p.resolve() # разрешает символические ссылки, возвращает абсолютный Path # Относительный путь p.relative_to("/home/user") # Path("file.txt") ```

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

#
## Чтение и запись файлов Объекты `Path` могут читать и записывать текст и байты напрямую — для простых случаев `open()` не нужен: ```python from pathlib import Path p = Path("notes.txt") # Запись текста p.write_text("Hello, pathlib!\n", encoding="utf-8") # Чтение текста content = p.read_text(encoding="utf-8") print(content) # Hello, pathlib! # Чтение байтов data = p.read_bytes() # Запись байтов p.write_bytes(b"\x00\x01\x02") ``` Для чтения строка за строкой используйте `open()` с Path: ```python with p.open("r", encoding="utf-8") as f: for line in f: print(line.strip()) ``` ## Просмотр содержимого директории ```python d = Path("/home/user/projects") # Все записи (файлы и поддиректории) for item in d.iterdir(): print(item.name, "dir" if item.is_dir() else "file") # Только файлы files = [f for f in d.iterdir() if f.is_file()] # Только директории subdirs = [f for f in d.iterdir() if f.is_dir()] ``` ## glob: поиск по паттерну ```python src = Path("/home/user/projects") # Все Python-файлы в директории (без рекурсии) py_files = list(src.glob("*.py")) # Все Python-файлы рекурсивно (** = любое количество директорий) all_py = list(src.rglob("*.py")) # Все лог-файлы в любой поддиректории "logs" logs = list(src.glob("**/logs/*.log")) ``` ## Создание директорий ```python new_dir = Path("/home/user/projects/new_feature") # Создать одну директорию new_dir.mkdir() # Создать родительские директории если не существуют, без ошибки если уже есть new_dir.mkdir(parents=True, exist_ok=True) ``` ## Переименование и удаление ```python p = Path("old_name.txt") # Переименовать (возвращает новый Path) new_p = p.rename("new_name.txt") print(new_p) # new_name.txt # Удалить файл p.unlink() # Удалить пустую директорию d = Path("empty_dir") d.rmdir() # Python 3.8+: missing_ok p.unlink(missing_ok=True) # без ошибки если не найден ``` ## Практический пример: обработать все CSV-файлы ```python data_dir = Path("data") output_dir = Path("output") output_dir.mkdir(exist_ok=True) for csv_file in data_dir.glob("*.csv"): content = csv_file.read_text() processed = content.upper() # условная трансформация out_file = output_dir / csv_file.name out_file.write_text(processed) print(f"Обработано: {csv_file.name}") ```

Справочник: pathlib

#
## Атрибуты Path (только для чтения) | Атрибут | Пример значения | Что возвращает | |---|---|---| | `p.name` | `"script.py"` | Имя файла с расширением | | `p.stem` | `"script"` | Имя файла без расширения | | `p.suffix` | `".py"` | Последнее расширение (с точкой) | | `p.suffixes` | `[".tar", ".gz"]` | Все расширения | | `p.parent` | `Path("/home/user")` | Path родительской директории | | `p.parents` | (итерируемый) | Все родительские директории | | `p.parts` | `("/", "home", "user", "f.py")` | Кортеж компонентов пути | | `p.root` | `"/"` | Корневой компонент | ## Методы Path — запросы | Метод | Что делает | |---|---| | `p.exists()` | True если путь существует (файл или директория) | | `p.is_file()` | True если существует и является обычным файлом | | `p.is_dir()` | True если существует и является директорией | | `p.is_symlink()` | True если символическая ссылка | | `p.stat()` | os.stat_result (размер, mtime и т.д.) | | `p.resolve()` | Абсолютный путь с разрешёнными ссылками | | `p.relative_to(other)` | Относительный путь от `other` | | `p.with_name(name)` | Новый путь с другим именем | | `p.with_stem(stem)` | Новый путь с другим stem | | `p.with_suffix(suf)` | Новый путь с другим расширением | ## Методы Path — файловый ввод/вывод | Метод | Что делает | |---|---| | `p.read_text(encoding=...)` | Прочитать содержимое как str | | `p.write_text(s, encoding=...)` | Записать str | | `p.read_bytes()` | Прочитать содержимое как bytes | | `p.write_bytes(b)` | Записать bytes | | `p.open(mode, encoding=...)` | Открыть файловый объект | ## Методы Path — операции с ФС | Метод | Что делает | |---|---| | `p.mkdir(parents=False, exist_ok=False)` | Создать директорию | | `p.rename(target)` | Переименовать, вернуть новый Path | | `p.replace(target)` | Переименовать, перезаписать если есть | | `p.unlink(missing_ok=False)` | Удалить файл или ссылку | | `p.rmdir()` | Удалить пустую директорию | | `p.touch()` | Создать пустой файл или обновить mtime | | `p.iterdir()` | Итерировать по содержимому директории | | `p.glob(pattern)` | Найти файлы по паттерну | | `p.rglob(pattern)` | Рекурсивный glob | ## Паттерны glob | Паттерн | Что соответствует | |---|---| | `*` | Любое имя в текущей директории | | `*.py` | Python-файлы в текущей директории | | `**/*.py` | Python-файлы рекурсивно | | `??.txt` | Двухсимвольное имя + .txt |
01

Атрибуты Path

#

Имея строку пути `"/home/user/projects/data/report_2024.csv"`, создайте объект `Path` из неё и выведите: - Полное имя файла (`name`) - Имя без расширения (`stem`) - Расширение (`suffix`) - Родительскую директорию (`parent`) - Все компоненты пути (`parts`)

from pathlib import Path

p = Path("/home/user/projects/data/report_2024.csv")

# Выведите: name, stem, suffix, parent, parts
print(p.name)    # ???
print(p.stem)    # ???
print(p.suffix)  # ???
print(p.parent)  # ???
print(p.parts)   # ???
Решение
from pathlib import Path

p = Path("/home/user/projects/data/report_2024.csv")

print(p.name)    # report_2024.csv
print(p.stem)    # report_2024
print(p.suffix)  # .csv
print(p.parent)  # /home/user/projects/data
print(p.parts)   # ("/", "home", "user", "projects", "data", "report_2024.csv")
02

Построение пути через /

#

Напишите функцию `build_path(base, *parts)`, которая принимает базовую директорию как строку и любое количество частей пути, соединяет их через оператор `/` и возвращает итоговый объект `Path`. Пример: `build_path("/home/user", "projects", "myapp", "config.json")` → `Path("/home/user/projects/myapp/config.json")`.

from pathlib import Path


def build_path(base, *parts):
    pass


result = build_path("/home/user", "projects", "myapp", "config.json")
print(result)   # /home/user/projects/myapp/config.json
Решение
from pathlib import Path


def build_path(base, *parts):
    p = Path(base)
    for part in parts:
        p = p / part
    return p


# Короче: Path(base).joinpath(*parts)
result = build_path("/home/user", "projects", "myapp", "config.json")
print(result)   # /home/user/projects/myapp/config.json
03

Изменить расширение

#

Напишите функцию `change_extension(path_str, new_ext)`, которая принимает строку пути и новое расширение (с точкой, например `".txt"`), и возвращает новый `Path` с изменённым расширением. Пример: `change_extension("/home/user/data.csv", ".json")` → `Path("/home/user/data.json")`. Используйте `p.with_suffix()`.

from pathlib import Path


def change_extension(path_str, new_ext):
    pass


print(change_extension("/home/user/data.csv", ".json"))
# /home/user/data.json
print(change_extension("/docs/report.txt", ".md"))
# /docs/report.md
Решение
from pathlib import Path


def change_extension(path_str, new_ext):
    return Path(path_str).with_suffix(new_ext)


print(change_extension("/home/user/data.csv", ".json"))
# /home/user/data.json
print(change_extension("/docs/report.txt", ".md"))
# /docs/report.md
04

Безопасное чтение файла

#

Напишите функцию `safe_read(path_str, encoding="utf-8")`, которая читает содержимое файла по `path_str` и возвращает его как строку. Если файл не существует — возвращает `None`. При любой другой ошибке — тоже `None`. Используйте `Path.read_text()`.

from pathlib import Path


def safe_read(path_str, encoding="utf-8"):
    pass


Path("/tmp/test_pathlib.txt").write_text("hello", encoding="utf-8")
print(safe_read("/tmp/test_pathlib.txt"))   # hello
print(safe_read("/tmp/missing.txt"))        # None
Решение
from pathlib import Path


def safe_read(path_str, encoding="utf-8"):
    try:
        return Path(path_str).read_text(encoding=encoding)
    except Exception:
        return None


Path("/tmp/test_pathlib.txt").write_text("hello", encoding="utf-8")
print(safe_read("/tmp/test_pathlib.txt"))   # hello
print(safe_read("/tmp/missing.txt"))        # None
05

Записать и проверить

#

Напишите функцию `write_and_verify(path_str, content)`, которая: 1. Записывает `content` в файл по `path_str` через `Path.write_text()` 2. Читает обратно через `Path.read_text()` 3. Возвращает `True` если содержимое совпадает, `False` иначе Используйте `encoding="utf-8"` для обеих операций.

from pathlib import Path


def write_and_verify(path_str, content):
    pass


print(write_and_verify("/tmp/verify_test.txt", "Hello pathlib!"))
# True
Решение
from pathlib import Path


def write_and_verify(path_str, content):
    p = Path(path_str)
    p.write_text(content, encoding="utf-8")
    return p.read_text(encoding="utf-8") == content


print(write_and_verify("/tmp/verify_test.txt", "Hello pathlib!"))
# True
06

Подсчёт файлов по расширению

#

Напишите функцию `count_by_extension(dir_path)`, которая принимает строку пути к директории и возвращает словарь, где каждое расширение файла соответствует количеству файлов с этим расширением в директории (без рекурсии). Пример: если директория содержит `a.py`, `b.py`, `c.txt`, `d.txt`, `e.md`, результат: `{".py": 2, ".txt": 2, ".md": 1}`.

from pathlib import Path


def count_by_extension(dir_path):
    pass


# Пример: если в директории a.py, b.py, c.txt, d.txt, e.md
# результат: {".py": 2, ".txt": 2, ".md": 1}
Решение
from pathlib import Path


def count_by_extension(dir_path):
    counts = {}
    for p in Path(dir_path).iterdir():
        if p.is_file():
            ext = p.suffix
            counts[ext] = counts.get(ext, 0) + 1
    return counts


import tempfile
with tempfile.TemporaryDirectory() as d:
    for name in ["a.py", "b.py", "c.txt", "d.txt", "e.md"]:
        Path(d, name).write_text("", encoding="utf-8")
    print(count_by_extension(d))
    # {".py": 2, ".txt": 2, ".md": 1}
07

Найти большие файлы

#

Напишите функцию `find_large_files(dir_path, min_size_bytes)`, которая рекурсивно ищет в директории все файлы больше `min_size_bytes` и возвращает их пути как отсортированный список объектов `Path`. Используйте `p.rglob("*")` и `p.stat().st_size`.

from pathlib import Path


def find_large_files(dir_path, min_size_bytes):
    pass


# Возвращает отсортированный список Path-объектов для файлов > min_size_bytes
Решение
from pathlib import Path


def find_large_files(dir_path, min_size_bytes):
    return sorted(
        p for p in Path(dir_path).rglob("*")
        if p.is_file() and p.stat().st_size > min_size_bytes
    )


for f in find_large_files(".", 1000):
    print(f)
08

Гарантировать директорию

#

Напишите функцию `ensure_dir(path_str)`, которая создаёт директорию по `path_str` если она не существует. Она должна: - Создавать все промежуточные родительские директории по необходимости - Не бросать ошибку если директория уже существует - Возвращать объект `Path` директории Используйте `Path.mkdir(parents=True, exist_ok=True)`.

from pathlib import Path


def ensure_dir(path_str):
    pass


p = ensure_dir("/tmp/myapp/logs/2024")
print(p.is_dir())   # True
print(p)            # /tmp/myapp/logs/2024
Решение
from pathlib import Path


def ensure_dir(path_str):
    p = Path(path_str)
    p.mkdir(parents=True, exist_ok=True)
    return p


p = ensure_dir("/tmp/myapp/logs/2024")
print(p.is_dir())   # True
print(p)            # /tmp/myapp/logs/2024
09

Резервная копия файла

#

Напишите функцию `backup(path_str)`, которая создаёт резервную копию файла добавляя `.bak` к его имени. Если файл не существует — бросает `FileNotFoundError`. Возвращает `Path` резервного файла. Пример: `backup("/home/user/data.txt")` создаёт `/home/user/data.txt.bak` и возвращает этот путь.

from pathlib import Path
import shutil


def backup(path_str):
    pass


Path("/tmp/data_original.txt").write_text("important", encoding="utf-8")
bak = backup("/tmp/data_original.txt")
print(bak)   # /tmp/data_original.txt.bak
Решение
from pathlib import Path
import shutil


def backup(path_str):
    p = Path(path_str)
    if not p.exists():
        raise FileNotFoundError(f"File not found: {p}")
    bak = p.with_suffix(p.suffix + ".bak")
    shutil.copy2(str(p), str(bak))
    return bak


Path("/tmp/data_original.txt").write_text("important", encoding="utf-8")
bak = backup("/tmp/data_original.txt")
print(bak)   # /tmp/data_original.txt.bak
print(bak.read_text(encoding="utf-8"))   # important
10

Найти Python-файлы

#

Напишите функцию `find_python_files(start_dir)`, которая рекурсивно находит все файлы `.py` в `start_dir`, сортирует их по имени и возвращает список их относительных путей (относительно `start_dir`) как строки. Используйте `Path.rglob("*.py")` и `p.relative_to(start_dir)`.

from pathlib import Path


def find_python_files(start_dir):
    pass


# Возвращает список относительных строк пути отсортированных по имени
for f in find_python_files("."):
    print(f)
Решение
from pathlib import Path


def find_python_files(start_dir):
    base = Path(start_dir)
    return sorted(
        str(p.relative_to(base))
        for p in base.rglob("*.py")
    )


for f in find_python_files("."):
    print(f)