07-06: pathlib and File System Operations¶
pathlib (Python 3.4+) provides an object-oriented, cross-platform way to work with file paths and the file system. It is modern Python's preferred replacement for string-based os.path operations.
The Path Object¶
from pathlib import Path
# Create a Path object — just a smart string for now
p = Path("folder/subfolder/file.txt")
# Parts
print(p.name) # 'file.txt' — filename with extension
print(p.stem) # 'file' — filename without extension
print(p.suffix) # '.txt' — extension
print(p.suffixes) # ['.txt']
print(p.parent) # folder/subfolder
print(p.parents[0]) # folder/subfolder
print(p.parents[1]) # folder
print(p.parts) # ('folder', 'subfolder', 'file.txt')
# Absolute path
print(Path.cwd()) # current working directory
print(Path.home()) # user's home directory e.g. C:\Users\alice
p_abs = Path("file.txt").resolve() # absolute path
print(p_abs)
Building Paths¶
from pathlib import Path
# Join with / operator (works on all OS!)
base = Path("C:/Users/alice")
full = base / "Documents" / "report.txt"
print(full) # C:\Users\alice\Documents\report.txt
# Equivalent to os.path.join
import os
old_way = os.path.join("C:\\Users\\alice", "Documents", "report.txt")
# Change extension
p = Path("image.png")
print(p.with_suffix(".jpg")) # image.jpg
print(p.with_stem("thumbnail")) # thumbnail.png
print(p.with_name("new.txt")) # new.txt
Checking Existence and Type¶
from pathlib import Path
p = Path("myfile.txt")
print(p.exists()) # True/False — path exists at all
print(p.is_file()) # True if it's a file (not a directory)
print(p.is_dir()) # True if it's a directory
print(p.is_symlink()) # True if it's a symbolic link
print(p.is_absolute())# True if absolute path
# Safe access pattern
if p.exists():
print(p.read_text())
else:
print("File not found")
Reading and Writing Files with pathlib¶
from pathlib import Path
p = Path("notes.txt")
# Write (overwrites!)
p.write_text("Hello, World!\nSecond line", encoding="utf-8")
# Read entire file
content = p.read_text(encoding="utf-8")
print(content)
# Read binary
data = p.read_bytes()
# Write binary
p.write_bytes(b"\x89PNG\r\n")
# Append — pathlib doesn't have append directly; use open()
with p.open("at", encoding="utf-8") as f:
f.write("\nThird line")
Creating Directories¶
from pathlib import Path
# Create a single directory
Path("new_folder").mkdir()
# Create nested directories
Path("a/b/c/d").mkdir(parents=True, exist_ok=True)
# parents=True → create all missing parent dirs
# exist_ok=True → don't raise error if dir already exists
# Create from Path
base = Path.home() / "projects" / "myapp"
base.mkdir(parents=True, exist_ok=True)
(base / "data").mkdir(exist_ok=True)
(base / "output").mkdir(exist_ok=True)
Listing Directory Contents¶
from pathlib import Path
folder = Path(".")
# List all items in a directory
for item in folder.iterdir():
print(item.name, "DIR" if item.is_dir() else "FILE")
# List only files
files = [p for p in folder.iterdir() if p.is_file()]
# List only directories
dirs = [p for p in folder.iterdir() if p.is_dir()]
# glob — match files by pattern
for f in folder.glob("*.txt"):
print(f)
for f in folder.glob("*.py"):
print(f)
# rglob — recursive glob (all subdirectories)
for f in folder.rglob("*.py"):
print(f) # finds all .py files everywhere under folder
# Find all .csv files under a project
project = Path("my_project")
csvs = list(project.rglob("*.csv"))
print(f"Found {len(csvs)} CSV files")
File Metadata¶
from pathlib import Path
from datetime import datetime
p = Path("document.pdf")
stat = p.stat()
print(f"Size: {stat.st_size:,} bytes")
print(f"Size: {stat.st_size / 1024:.1f} KB")
print(f"Modified: {datetime.fromtimestamp(stat.st_mtime)}")
print(f"Created: {datetime.fromtimestamp(stat.st_ctime)}")
print(f"Accessed: {datetime.fromtimestamp(stat.st_atime)}")
Copying, Moving, and Deleting¶
from pathlib import Path
import shutil
src = Path("source.txt")
dest = Path("destination.txt")
# Copy a file
shutil.copy2(src, dest) # copy2 preserves metadata
shutil.copy(src, dest) # copy without metadata
# Copy entire directory tree
shutil.copytree("source_dir", "backup_dir")
# Move / rename
src.rename(Path("renamed.txt")) # rename in same directory
src.replace(Path("other/file.txt")) # move (and overwrite if exists)
# Using shutil.move (more robust)
shutil.move("old_name.txt", "new_name.txt")
shutil.move("file.txt", "subdir/") # move into subdir
# Delete a file
p = Path("to_delete.txt")
p.unlink() # delete file
p.unlink(missing_ok=True) # don't error if already gone (3.8+)
# Delete empty directory
Path("empty_dir").rmdir()
# Delete directory and all contents (careful!)
shutil.rmtree("directory_to_delete")
# Safe delete pattern
if p.exists():
p.unlink()
Practical Patterns¶
Find the largest files¶
from pathlib import Path
folder = Path(".")
files = [(p, p.stat().st_size) for p in folder.rglob("*") if p.is_file()]
files.sort(key=lambda x: x[1], reverse=True)
print("Top 5 largest files:")
for path, size in files[:5]:
print(f" {size/1024:8.1f} KB {path}")
Organize files by extension¶
from pathlib import Path
import shutil
source = Path("Downloads")
dest = Path("Sorted")
for file in source.iterdir():
if file.is_file():
ext_folder = dest / file.suffix.lstrip(".").upper()
ext_folder.mkdir(parents=True, exist_ok=True)
shutil.copy2(file, ext_folder / file.name)
print(f" {file.name} → {ext_folder.name}/")
Batch rename files¶
from pathlib import Path
folder = Path("photos")
for i, file in enumerate(sorted(folder.glob("*.jpg")), start=1):
new_name = folder / f"photo_{i:04d}.jpg"
file.rename(new_name)
print(f" {file.name} → {new_name.name}")
Count files and total size¶
from pathlib import Path
def directory_summary(path):
p = Path(path)
total_size = 0
counts = {}
for f in p.rglob("*"):
if f.is_file():
ext = f.suffix.lower() or "(no ext)"
counts[ext] = counts.get(ext, 0) + 1
total_size += f.stat().st_size
print(f"Total size: {total_size / 1_048_576:.1f} MB")
for ext, n in sorted(counts.items(), key=lambda x: -x[1]):
print(f" {ext:10s}: {n} files")
directory_summary(".")
pathlib vs os.path¶
| Task | os.path (old) |
pathlib (modern) |
|---|---|---|
| Join paths | os.path.join(a, b) |
Path(a) / b |
| File name | os.path.basename(p) |
p.name |
| Directory | os.path.dirname(p) |
p.parent |
| Extension | os.path.splitext(p)[1] |
p.suffix |
| Exists | os.path.exists(p) |
p.exists() |
| Is file | os.path.isfile(p) |
p.is_file() |
| Absolute | os.path.abspath(p) |
p.resolve() |
| Home dir | os.path.expanduser("~") |
Path.home() |
| Current dir | os.getcwd() |
Path.cwd() |
| Read text | manual open() |
p.read_text() |
| Write text | manual open() |
p.write_text() |
| List dir | os.listdir() |
p.iterdir() |
| Recursive glob | os.walk() |
p.rglob("*") |
| File size | os.path.getsize(p) |
p.stat().st_size |
Use pathlib for new code. It's more readable, cross-platform, and object-oriented.
Exercises: 07-06: Exercises — pathlib and File System Operations
⬅️ Previous: 07-05: Working with PDF Files ➡️ Next: 08-01: Modules and Imports