Stop Writing File Paths Like It’s 1999 — Python’s pathlib Is What You Actually Need
Beyond the Basics · Python Toolkit Series · #2
Here’s a line of Python we usually write:
import os
path = os.path.join(os.path.dirname(__file__), "data", "report.csv")
You’re importing os, calling os.path, calling os.path.join, calling os.path.dirname, passing __file__… to access “the data/report.csv file.”
With Path library there’s a better way to do it.
from pathlib import Path
path = Path(__file__).parent / "data" / "report.csv"
That / isn’t division — it’s pathlib’s way of joining paths.
What’s Wrong with os.path?
os.path has two problems:
**1. Your path is a string. You have to call a function every time you want to do anything with it — get its name, its extension, its parent folder. The path itself knows nothing.
2. The API is scattered. Need to read a file? That’s open(). Check if it exists? os.path.exists(). Get the filename? os.path.basename(). Create a folder? os.makedirs(). You’re jumping between os, os.path, and built-ins constantly.
pathlib fixes both. A Path object knows things about itself, and you call methods directly on it.
The Core Idea: Paths Are Objects, Not Strings
from pathlib import Path
p = Path("/home/user/projects/data/report.csv")
print(p.name) # report.csv
print(p.stem) # report (name without extension)
print(p.suffix) # .csv
print(p.parent) # /home/user/projects/data
print(p.parts) # ('/', 'home', 'user', 'projects', 'data', 'report.csv')
It gives easier way to understand the path.
Compare that to os.path:
import os
p = "/home/user/projects/data/report.csv"
print(os.path.basename(p)) # report.csv
print(os.path.splitext(os.path.basename(p))[0]) # report — look at this mess
print(os.path.splitext(p)[1]) # .csv
print(os.path.dirname(p)) # /home/user/projects/data
It takes much more effort just for basic details.
4 Places Where pathlib Wins Clearly
1. Building Paths
os.path:
config_file = os.path.join(os.path.expanduser("~"), ".config", "myapp", "settings.json")
pathlib:
config_file = Path.home() / ".config" / "myapp" / "settings.json"
pathlib uses / to define the path.
2. Reading and Writing Files
With os.path, you still need open(). With pathlib, the path itself can read and write:
from pathlib import Path
p = Path("notes.txt")
# Write
p.write_text("Hello, pathlib!")
# Read
content = p.read_text()
print(content) # Hello, pathlib!
No with open(...) as f, no f.read(). Just .write_text() and .read_text(). For quick file operations, It’s the hardest way.
3. Checking and Creating
os.path:
if not os.path.exists("logs"):
os.makedirs("logs")
pathlib:
Path("logs").mkdir(exist_ok=True)
exist_ok=True means “create it if it doesn’t exist, do nothing if it already does.” One line. No condition needed.
4. Finding Files (Glob)
This is where pathlib does better. Want every .csv file in a folder, including subfolders?
os.path way — you’d need os.walk(), write a loop, filter extensions manually.
pathlib way:
from pathlib import Path
data_dir = Path("data")
# All CSVs in this folder
for file in data_dir.glob("*.csv"):
print(file)
# All CSVs in this folder AND all subfolders
for file in data_dir.rglob("*.csv"):
print(file)
glob() and rglob() return path objects, not strings. So you can immediately call .name, .stem, .read_text().
Works on Windows and Mac/Linux — Automatically
This is the real advantage.
On Windows, paths use backslashes: C:\Users\user\data
On Mac/Linux, they use forward slashes: /home/user/data
When you use os.path.join(), you have to be careful. When you use pathlib, it handles this for you automatically. The same code works everywhere.
One Pattern You’ll Use Every Day
If you write Python scripts and work with files relative to where your script lives, this is the pattern:
from pathlib import Path
# This is the folder your script is in — not wherever you run it from
BASE_DIR = Path(__file__).parent
data_file = BASE_DIR / "data" / "input.csv"
output_file = BASE_DIR / "output" / "results.csv"
config_file = BASE_DIR / "config.json"
With os.path, this is os.path.join(os.path.dirname(os.path.abspath(__file__)), ...) — and you’d have to repeat it for every file. With pathlib, you define BASE_DIR once and build everything from it cleanly.
The Quick Reference
from pathlib import Path
p = Path("data/report.csv")
# Info about the path
p.name # report.csv
p.stem # report
p.suffix # .csv
p.parent # data/
p.exists() # True or False
# Building paths
new_path = p.parent / "archive" / p.name
# Reading and writing
p.write_text("content")
text = p.read_text()
p.write_bytes(b"bytes")
data = p.read_bytes()
# Creating folders
Path("logs").mkdir(parents=True, exist_ok=True)
# Finding files
list(Path(".").glob("*.py")) # current folder
list(Path(".").rglob("*.py")) # all subfolders too
# Useful extras
Path.home() # /home/username
Path.cwd() # current working directory
p.resolve() # absolute path, symlinks resolved
Should You Go Back and Rewrite Old Code?
Not necessarily. If your os.path code works, leave it. But for anything new you write — especially scripts that touch files — reach for pathlib first. The code will be shorter, cleaner, and easier to read later.
And if you’re working on a team? pathlib code is far easier to review. Path("data") / "report.csv" explains itself. os.path.join("data", "report.csv") makes you double-check what it’s doing.
The Bottom Line
os.path asks you to call a function for everything. pathlib lets the path carry its own meaning. Once you switch, string-based paths start looking like unnecessary friction.
It’s built into Python. No install needed.
from pathlib import Path
That’s the whole setup.
This is post #2 in the Beyond the Basics series — exploring Python libraries that show up in real work.
Post #1 covered rich — the library that turns your terminal output from noise into signal. Check it out if you missed it.
*Enjoyed this? The next post covers Typer — popular Python library designed to build clean, powerful command-line interface (CLI) applications using modern Python type hints