Paths & the File System
Working with files means working with paths: joining folder names, finding a file's extension, listing a directory, creating folders. The pathlib module represents paths as objects with clear methods, and works the same on Windows, macOS and Linux.
Path objects
from pathlib import Path p = Path("projects") / "reports" / "2026-q3.final.csv" print(p) print(p.name) print(p.stem) print(p.suffix, p.suffixes) print(p.parent) print(p.parts) print(p.with_suffix(".xlsx"))
projects/reports/2026-q3.final.csv
2026-q3.final.csv
2026-q3.final
.csv ['.final', '.csv']
projects/reports
('projects', 'reports', '2026-q3.final.csv')
projects/reports/2026-q3.final.xlsxThe / operator joins path parts using the correct separator for the operating system — \ on Windows, / elsewhere. No more gluing strings together.
Useful locations
from pathlib import Path print(Path.cwd()) # current working directory print(Path.home()) # the user's home folder print(Path(__file__).parent) # the folder containing this script
Building paths relative to Path(__file__).parent makes a script find its data files no matter which directory it's run from.
Creating, reading and writing
from pathlib import Path folder = Path("demo/output") folder.mkdir(parents=True, exist_ok=True) report = folder / "report.txt" report.write_text("Total: 42\n", encoding="utf-8") print(report.exists(), report.is_file(), folder.is_dir()) print(report.read_text(encoding="utf-8").strip()) print(report.stat().st_size, "bytes")
True True True Total: 42 10 bytes
mkdir(parents=True, exist_ok=True) creates any missing parent folders and doesn't complain if the folder already exists. write_text and read_text are one-line shortcuts for small files.
Listing and searching
from pathlib import Path base = Path("site") for name in ["index.html", "about.html", "css/main.css", "img/logo.png", "img/icons/x.svg"]: path = base / name path.parent.mkdir(parents=True, exist_ok=True) path.write_text("…", encoding="utf-8") print(sorted(p.name for p in base.iterdir())) print(sorted(p.name for p in base.glob("*.html"))) print(sorted(str(p.relative_to(base)) for p in base.rglob("*") if p.is_file() and p.suffix in {".png", ".svg"}))
['about.html', 'css', 'img', 'index.html'] ['about.html', 'index.html'] ['img/icons/x.svg', 'img/logo.png']
iterdir()lists a folder's direct contents.glob("*.html")matches a pattern in one folder;rglobsearches all subfolders recursively.relative_toshows a path relative to another.
Renaming, moving and deleting
from pathlib import Path import shutil work = Path("work") work.mkdir(exist_ok=True) draft = work / "draft.txt" draft.write_text("v1", encoding="utf-8") final = draft.rename(work / "final.txt") backup = Path(shutil.copy(final, work / "final.bak")) print(sorted(p.name for p in work.iterdir())) backup.unlink() shutil.rmtree(work) print(work.exists())
['final.bak', 'final.txt'] False
| Task | Use |
|---|---|
| Rename or move a file | path.rename(new) or shutil.move(src, dst) |
| Copy a file / a whole folder | shutil.copy(src, dst) / shutil.copytree(src, dst) |
| Delete a file | path.unlink(missing_ok=True) |
| Delete an empty folder / a folder and everything in it | path.rmdir() / shutil.rmtree(path) |
shutil.rmtree deletes everything inside a folder permanently, without using the recycle bin. Double-check the path before running it.The os module
Older code uses os and os.path for the same jobs. You'll still use os for environment variables and process information:
import os os.environ["APP_MODE"] = "test" print(os.environ.get("APP_MODE")) print(os.environ.get("MISSING_VAR", "default value")) print(os.path.join("a", "b", "c.txt")) print(os.path.splitext("photo.jpeg"))
test
default value
a/b/c.txt
('photo', '.jpeg')Environment variables are the standard way to pass configuration — like API keys and database URLs — to a program without hard-coding them.
Temporary files
import tempfile from pathlib import Path with tempfile.TemporaryDirectory() as tmp: scratch = Path(tmp) / "scratch.txt" scratch.write_text("temporary data", encoding="utf-8") print(scratch.read_text(encoding="utf-8")) print(scratch.exists())
temporary data False
The temporary folder and everything in it are deleted when the with block ends — ideal for tests and intermediate files.