Модуль pathlib предоставляет объектно-ориентированный API для работы с путями файловой системы. Он появился в Python 3.4 и стал стандартом де-факто, заменив процедурный os.path. Пути в pathlib -- это объекты с методами, а не строки с функциями.
Создание путей
from pathlib import Path
# Current directory
cwd = Path.cwd()
print(cwd) # /Users/developer/project
# Home directory
home = Path.home()
print(home) # /Users/developer
# From string
config = Path("/etc/nginx/nginx.conf")
# From parts
project = Path("src") / "mypackage" / "main.py"
print(project) # src/mypackage/main.py
# Operator / builds paths naturally
data_dir = Path.home() / "Documents" / "data"
print(data_dir) # /Users/developer/Documents/data
Свойства пути
from pathlib import Path
path = Path("/Users/developer/project/src/main.py")
print(path.name) # main.py — filename with extension
print(path.stem) # main — filename without extension
print(path.suffix) # .py — file extension
print(path.suffixes) # ['.py'] — all extensions
print(path.parent) # /Users/developer/project/src
print(path.parents[0]) # /Users/developer/project/src
print(path.parents[1]) # /Users/developer/project
print(path.parts) # ('/', 'Users', 'developer', 'project', 'src', 'main.py')
# Multiple extensions
archive = Path("data.tar.gz")
print(archive.suffixes) # ['.tar', '.gz']
print(archive.stem) # data.tar
Навигация и изменение путей
from pathlib import Path
path = Path("/project/src/main.py")
# Change filename
new_path = path.with_name("app.py")
print(new_path) # /project/src/app.py
# Change extension
test_path = path.with_suffix(".txt")
print(test_path) # /project/src/main.txt
# Change stem (Python 3.12+)
renamed = path.with_stem("module")
print(renamed) # /project/src/module.py
# Navigate up
parent = path.parent
print(parent) # /project/src
grandparent = path.parent.parent
print(grandparent) # /project
# Join paths
new_file = path.parent / "utils" / "helpers.py"
print(new_file) # /project/src/utils/helpers.py
Проверка существования и типа
from pathlib import Path
path = Path("pyproject.toml")
# Existence checks
print(path.exists()) # True/False
print(path.is_file()) # True if it's a file
print(path.is_dir()) # True if it's a directory
print(path.is_symlink()) # True if it's a symbolic link
print(path.is_absolute()) # True if path is absolute
# File info
if path.exists():
stat = path.stat()
print(f"Size: {stat.st_size} bytes")
print(f"Modified: {stat.st_mtime}")
# Human-readable size
from datetime import datetime
modified = datetime.fromtimestamp(stat.st_mtime)
print(f"Last modified: {modified}")
Чтение и запись файлов
pathlib предоставляет удобные однострочники для простых операций:
from pathlib import Path
config = Path("config.txt")
# Write text
config.write_text("host=localhost\nport=5432\n", encoding="utf-8")
# Read text
content = config.read_text(encoding="utf-8")
print(content)
# Write bytes
binary_path = Path("data.bin")
binary_path.write_bytes(b"\x89PNG\r\n\x1a\n")
# Read bytes
data = binary_path.read_bytes()
print(data[:4]) # b'\x89PNG'
Работа со строками
from pathlib import Path
# Read lines
lines = Path("data.txt").read_text(encoding="utf-8").splitlines()
for line in lines:
print(line)
# Write lines
output = Path("output.txt")
output.write_text("\n".join(["line 1", "line 2", "line 3"]), encoding="utf-8")
Сочетание с open()
Для более сложных операций используйте Path.open():
from pathlib import Path
log_path = Path("app.log")
# Path.open() works like built-in open()
with log_path.open("a", encoding="utf-8") as f:
f.write("New log entry\n")
# Read large file line by line
with log_path.open(encoding="utf-8") as f:
for line in f:
if "ERROR" in line:
print(line.strip())
Поиск файлов: glob и rglob
from pathlib import Path
project = Path("src")
# Find all Python files in directory
for py_file in project.glob("*.py"):
print(py_file)
# Recursive search — all Python files in all subdirectories
for py_file in project.rglob("*.py"):
print(py_file)
# Pattern matching
for config in Path("/etc").glob("*.conf"):
print(config)
# Multiple patterns
from itertools import chain
patterns = ["*.py", "*.toml", "*.yml"]
all_files = chain.from_iterable(project.rglob(p) for p in patterns)
# Complex patterns
for test_file in project.rglob("test_*.py"):
print(f"Test: {test_file}")
Фильтрация результатов
from pathlib import Path
src = Path("src")
# Only files (not directories)
python_files = [p for p in src.rglob("*.py") if p.is_file()]
# Files larger than 1KB
large_files = [
p for p in src.rglob("*")
if p.is_file() and p.stat().st_size > 1024
]
# Sort by modification time
recent_files = sorted(
src.rglob("*.py"),
key=lambda p: p.stat().st_mtime,
reverse=True
)[:5] # 5 most recently modified
Создание и удаление
from pathlib import Path
# Create directory (and parents if needed)
data_dir = Path("data") / "processed" / "2024"
data_dir.mkdir(parents=True, exist_ok=True)
# Create a file
file_path = data_dir / "results.txt"
file_path.touch() # Create empty file (or update modification time)
# Delete a file
file_path.unlink()
# Delete file if exists (Python 3.8+)
file_path.unlink(missing_ok=True)
# Delete empty directory
data_dir.rmdir()
# Delete directory tree — use shutil
import shutil
shutil.rmtree(Path("data"))
Переименование и перемещение
from pathlib import Path
source = Path("old_name.txt")
source.write_text("content", encoding="utf-8")
# Rename (same directory)
new_path = source.rename("new_name.txt")
print(new_path) # new_name.txt
# Move to another directory
dest = Path("archive") / "new_name.txt"
Path("archive").mkdir(exist_ok=True)
moved = new_path.rename(dest)
# Replace (overwrite if exists)
backup = Path("backup.txt")
backup.write_text("old backup", encoding="utf-8")
dest.replace(backup) # Overwrites backup.txt
Сравнение: os.path vs pathlib
import os
from pathlib import Path
# --- Joining paths ---
# os.path
result = os.path.join("/home", "user", "docs", "file.txt")
# pathlib
result = Path("/home") / "user" / "docs" / "file.txt"
# --- Getting filename ---
# os.path
name = os.path.basename("/home/user/docs/file.txt")
# pathlib
name = Path("/home/user/docs/file.txt").name
# --- Checking existence ---
# os.path
exists = os.path.exists("/home/user/docs")
# pathlib
exists = Path("/home/user/docs").exists()
# --- Reading a file ---
# os.path style
with open(os.path.join("data", "config.txt")) as f:
content = f.read()
# pathlib style
content = (Path("data") / "config.txt").read_text(encoding="utf-8")
# --- Walking directories ---
# os.walk
for root, dirs, files in os.walk("src"):
for file in files:
filepath = os.path.join(root, file)
print(filepath)
# pathlib
for filepath in Path("src").rglob("*"):
if filepath.is_file():
print(filepath)
# --- Getting extension ---
# os.path
_, ext = os.path.splitext("archive.tar.gz") # '.gz'
# pathlib
ext = Path("archive.tar.gz").suffix # '.gz'
exts = Path("archive.tar.gz").suffixes # ['.tar', '.gz']
Практический пример: организатор файлов
from pathlib import Path
from collections import defaultdict
def organize_by_extension(source_dir: str, target_dir: str) -> dict[str, int]:
"""Organize files into subdirectories by extension."""
source = Path(source_dir)
target = Path(target_dir)
stats: dict[str, int] = defaultdict(int)
for file_path in source.iterdir():
if not file_path.is_file():
continue
# Determine target subdirectory
ext = file_path.suffix.lower().lstrip(".")
if not ext:
ext = "no_extension"
# Create subdirectory and move file
ext_dir = target / ext
ext_dir.mkdir(parents=True, exist_ok=True)
dest = ext_dir / file_path.name
file_path.rename(dest)
stats[ext] += 1
return dict(stats)
def find_duplicates(directory: str) -> dict[int, list[Path]]:
"""Find files with the same size (potential duplicates)."""
size_map: dict[int, list[Path]] = defaultdict(list)
for file_path in Path(directory).rglob("*"):
if file_path.is_file():
size = file_path.stat().st_size
size_map[size].append(file_path)
# Return only groups with duplicates
return {size: paths for size, paths in size_map.items() if len(paths) > 1}