Skip to Content
Python Intermediate: Write Code You Won't Be Embarrassed to Share
Intermediate
0 likes
Courses
Python Intermediate: Write Code You Won't Be Embarrassed to Share

Python pathlib: Stop Building File Paths by Hand

Build, check and search file paths with pathlib instead of string concatenation. After this, the same script works on Windows, macOS and Linux.

Course content

Sign in to save your progress.

Course Home

Stop gluing file paths together with + and "/". Use pathlib. It is in the standard library, it handles Windows and macOS/Linux differences for you, and the code reads like what it does.

Skip ahead: If you already use Path, the / operator, glob and Path(__file__).parent, continue to "Reading, Cleaning and Writing CSV Data in Python."

Why this matters

String paths break in quiet ways. folder + "data.csv" forgets the slash and gives reportsdata.csv. A hard-coded "C:\\Users\\..." only works on one computer. A backslash in a normal string turns "\t" into a tab. None of these crash where the mistake is. They crash later, at the open() call, with a path that looks almost right.

Building paths

from pathlib import Path

base = Path("reports")
monthly = base / "2026" / "october" / "sales.csv"

print(monthly)
print(monthly.name)
print(monthly.stem)
print(monthly.suffix)
print(monthly.parent)
reports/2026/october/sales.csv
sales.csv
sales
.csv
reports/2026/october

The / operator joins parts with the right separator for the system. On Windows, the same code prints reports\2026\october\sales.csv. You don't write either separator yourself.

Changing part of a path

from pathlib import Path

source = Path("exports/orders_2026-10.csv")
print(source.with_suffix(".json"))
print(source.with_name("orders_clean.csv"))
print(source.with_stem(source.stem + "_backup"))
exports/orders_2026-10.json
exports/orders_clean.csv
exports/orders_2026-10_backup.csv

No slicing strings, no counting characters, no bugs when a file name has two dots. (with_stem needs Python 3.9 or newer.)

Checking and creating

from pathlib import Path

out_dir = Path("output") / "2026"
print(out_dir.exists())

out_dir.mkdir(parents=True, exist_ok=True)
print(out_dir.exists(), out_dir.is_dir())

summary = out_dir / "summary.txt"
summary.write_text("3 orders processed\n", encoding="utf-8")
print(summary.read_text(encoding="utf-8"), end="")
False
True True
3 orders processed

parents=True creates the missing folders in between. exist_ok=True means no error when the folder is already there, so the script can run twice. read_text and write_text open and close the file for you. They are fine for small files. For big files or line-by-line work, use path.open() in a with block, exactly like the previous lesson.

Finding files

from pathlib import Path

inbox = Path("inbox")
(inbox / "2026").mkdir(parents=True, exist_ok=True)
for name in ["a.csv", "b.csv", "notes.txt", "2026/c.csv"]:
    (inbox / name).write_text("x\n", encoding="utf-8")

print(sorted(p.name for p in inbox.glob("*.csv")))
print(sorted(str(p) for p in inbox.rglob("*.csv")))
['a.csv', 'b.csv']
['inbox/2026/c.csv', 'inbox/a.csv', 'inbox/b.csv']

glob searches one folder. rglob also searches every subfolder. The order of results isn't guaranteed, so sort them if the order matters, for example when processing files by date in the name.

The fix for "file not found, but it's right there"

A relative path like Path("data.csv") is relative to the folder you ran Python from, not the folder the script is in. Run the script from somewhere else, or from a scheduled task, and it looks in the wrong place. To find files that live next to the script, start from the script's own location:

id,total
1,1200
# tool/report.py
from pathlib import Path

HERE = Path(__file__).resolve().parent
data_file = HERE / "data.csv"

print(data_file.name, data_file.exists())
print(Path("data.csv").exists())
data.csv True
False

Run from the folder above tool, the plain relative path fails while the HERE version still finds the file. resolve() turns the path into a full absolute path, so it doesn't matter where you started.

⚠ Warning: Be careful with code that deletes files found by glob or rglob, like a cleanup that calls unlink() on every match. Print the list first and check it. A wrong base folder, or an rglob where you meant glob, deletes far more than you intended, and deleted files don't go to the Recycle Bin.

Windows paths in strings

If you must write a Windows path in your code, use a raw string or forward slashes. Both work on Windows:

print(len("C:\temp\new"))     # \t and \n became a tab and a newline
print(len(r"C:\temp\new"))    # raw string: backslashes kept
print("C:/temp/new")           # forward slashes also work on Windows
9
11
C:/temp/new

Better still, don't hard-code full paths at all. Build them from Path.home(), from HERE, or from a setting.

Common mistakes

  • Converting back to strings too early. Most functions, including open(), accept Path objects. Keep it a Path until something truly needs a string, then use str(path).
  • Forgetting exist_ok=True. The script works the first time and fails with FileExistsError the second time.
  • Mixing os.path and pathlib in the same function. It works, but it is harder to read. Pick one per project. For new code, pick pathlib.

💡 Tip: When a path is wrong and you can't see why, print path.resolve(). It shows the full path Python is actually using, which usually makes the problem obvious.

What happens if you skip this step

Your paths work on the computer you wrote them on, from the folder you usually run them from. The first time someone runs the script on another operating system, from another folder, or from a scheduler, it fails with "file not found" while the file is clearly sitting right there.

If you moved your project folder to another drive today, how many paths in your code would you need to change?

About

Build, check and search file paths with pathlib instead of string concatenation. After this, the same script works on Windows, macOS and Linux.

Duration
5 min
Last update
09/21/2026
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.