Python · Syntax · Intermediate
pathlib
Learn to work with file system paths using Python's modern pathlib module — cleaner and more powerful than os.path.
Quick topic start and explanations before exercises (exercises below):
Reading, Writing & Searching Files
#pathlib Reference
#Exercises:
Path Attributes
#Given the path string `"/home/user/projects/data/report_2024.csv"`, create a `Path` object from it and print: - The full filename (`name`) - The filename without extension (`stem`) - The extension (`suffix`) - The parent directory (`parent`) - All parts of the path (`parts`)
from pathlib import Path
p = Path("/home/user/projects/data/report_2024.csv")
# Print: name, stem, suffix, parent, parts
print(p.name) # ???
print(p.stem) # ???
print(p.suffix) # ???
print(p.parent) # ???
print(p.parts) # ???
Solution
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")
Build Path with /
#Write a function `build_path(base, *parts)` that takes a base directory as a string and any number of path parts, joins them using the `/` operator, and returns the final `Path` object. Example: `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
Solution
from pathlib import Path
def build_path(base, *parts):
p = Path(base)
for part in parts:
p = p / part
return p
# Shorter: Path(base).joinpath(*parts)
result = build_path("/home/user", "projects", "myapp", "config.json")
print(result) # /home/user/projects/myapp/config.json
Change Extension
#Write a function `change_extension(path_str, new_ext)` that takes a path string and a new extension (with dot, like `".txt"`), and returns a new `Path` with the extension changed. Example: `change_extension("/home/user/data.csv", ".json")` → `Path("/home/user/data.json")`. Use `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
Solution
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
Safe Read Text
#Write a function `safe_read(path_str, encoding="utf-8")` that reads the content of a file at `path_str` and returns it as a string. If the file does not exist, return `None`. If any other error occurs, return `None`. Use `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
Solution
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
Write and Read Back
#Write a function `write_and_verify(path_str, content)` that: 1. Writes `content` to the file at `path_str` using `Path.write_text()` 2. Reads it back using `Path.read_text()` 3. Returns `True` if the content matches, `False` otherwise Use `encoding="utf-8"` for both operations.
from pathlib import Path
def write_and_verify(path_str, content):
pass
print(write_and_verify("/tmp/verify_test.txt", "Hello pathlib!"))
# True
Solution
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
Count Files by Extension
#Write a function `count_by_extension(dir_path)` that takes a directory path string and returns a dictionary mapping each file extension to the count of files with that extension in the directory (non-recursive). Example: if the dir has `a.py`, `b.py`, `c.txt`, `d.txt`, `e.md`, result is `{".py": 2, ".txt": 2, ".md": 1}`.
from pathlib import Path
def count_by_extension(dir_path):
pass
# Example: if dir has a.py, b.py, c.txt, d.txt, e.md
# result: {".py": 2, ".txt": 2, ".md": 1}
Solution
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
# Test with a temp directory
import tempfile, os
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}
Find Large Files
#Write a function `find_large_files(dir_path, min_size_bytes)` that recursively searches a directory for all files larger than `min_size_bytes` and returns their paths as a sorted list of `Path` objects. Use `p.rglob("*")` and `p.stat().st_size`.
from pathlib import Path
def find_large_files(dir_path, min_size_bytes):
pass
# Returns sorted list of Path objects for files > min_size_bytes
Solution
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
)
# Test
for f in find_large_files(".", 1000):
print(f)
Ensure Directory
#Write a function `ensure_dir(path_str)` that creates the directory at `path_str` if it does not exist. It should: - Create all intermediate parent directories as needed - Not raise an error if the directory already exists - Return the `Path` object of the directory Use `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
Solution
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
Backup File
#Write a function `backup(path_str)` that creates a backup copy of a file by appending `.bak` to its name. If the file does not exist, raise `FileNotFoundError`. Return the `Path` of the backup file. Example: `backup("/home/user/data.txt")` creates `/home/user/data.txt.bak` and returns that path.
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
Solution
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
Find Python Files
#Write a function `find_python_files(start_dir)` that recursively finds all `.py` files under `start_dir`, sorts them by name, and returns a list of their relative paths (relative to `start_dir`) as strings. Use `Path.rglob("*.py")` and `p.relative_to(start_dir)`.
from pathlib import Path
def find_python_files(start_dir):
pass
# Returns list of relative path strings sorted by name
for f in find_python_files("."):
print(f)
Solution
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)