Lesson 22 · Intermediate · 19 min

Python Paths with pathlib

Treat file paths as objects instead of fragile strings.

pathlibfile pathssuffixesUTF-8 text

Yesterday's receipts sit in a folder whose name changes every day. String paths break on the backslash; a Path object does not. How do you list the files without guessing the separator?

The pathlib module turns a path into an object with useful parts and methods. It handles Windows and POSIX separators, avoids most string concatenation bugs, and makes reading or writing a small text file a one-line operation. You will also replace path parts, list files deterministically, and reject directory traversal.

Build a path with /

The slash operator joins path parts. It is not division here; Path overloads / for joining. Keep each part as a plain string and let pathlib choose the separator for the current operating system.

from pathlib import Path
path = Path("data") / "scores.csv"
print(path.as_posix())
print(path.name)

Join without string arithmetic

Inspect the parts

name is the final component, stem is the name without its last suffix, and suffix includes the dot. parent returns the containing directory. These properties are easier to read than a chain of split calls.

from pathlib import Path
path = Path("reports/2026-summary.txt")
print(path.stem)
print(path.suffix)
print(path.parent.as_posix())

Names, suffixes, and parents

Read and write UTF-8 text

write_text creates or replaces a file, and read_text returns its contents. Pass encoding='utf-8' explicitly so the result does not depend on the machine locale. Clean up temporary files when an example is finished.

from pathlib import Path
path = Path("pypractice-note.txt")
path.write_text("hello\n", encoding="utf-8")
print(path.read_text(encoding="utf-8").strip())
path.unlink()

A small text file

Change one part without string surgery

with_name and with_suffix replace one component while keeping the rest of the path. They are safer than replace() on the whole string, which can hit a directory name by accident. The original Path is immutable; each call returns a new one.

from pathlib import Path
path = Path("reports/2026-summary.txt")
print(path.with_name("2027-summary.txt").as_posix())
print(path.with_suffix(".md").as_posix())
print((path.parent / "archive" / path.name).as_posix())

Replace a name, suffix, or parent

List files deterministically

glob() returns paths in filesystem order, which is not guaranteed to be sorted. Sort before showing results or comparing them. TemporaryDirectory gives the example a private directory and removes it even when the block exits with an error.

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as tmp:
    root = Path(tmp)
    (root / "b.txt").write_text("tea", encoding="utf-8")
    (root / "a.txt").write_text("latte", encoding="utf-8")
    (root / "skip.md").write_text("x", encoding="utf-8")
    for path in sorted(root.glob("*.txt")):
        print(path.name, path.read_text(encoding="utf-8"))

Sort the glob, not the filesystem

A form value is not a safe path

Path(raw).name keeps only the final component, so ../secret.txt cannot escape through that value. That is a start, not a complete security policy: use an allowlist, resolve the final path, and check that it stays inside the intended root before opening it.

from pathlib import Path
for raw in ["notes.txt", "../secret.txt", "/etc/passwd"]:
    safe_name = Path(raw).name
    print(raw, "->", safe_name)

Strip directories before joining

Key points

  • Path('folder') / 'file.txt' joins path parts safely.
  • name, stem, suffix, and parent expose the path's parts.
  • read_text and write_text default to the platform encoding unless you pass encoding.
  • Use as_posix() when output must always use forward slashes.
  • Use Path methods for components; never join untrusted text directly to a root.

Common mistakes

  • Joining paths with string + and forgetting the separator.
  • Printing a Path when a stable, portable string is required.
  • Reading or writing text without specifying UTF-8.
  • Using replace() on a whole path and accidentally changing a parent directory name.
Your turn

Create Path('data') / 'scores.csv' and print its name, suffix, and parent as a POSIX path.

Try it yourself

Change the values, predict the output, then run the code. The Python runtime is downloaded the first time you press Run, so the first run can take a few seconds.

In-browser PythonRuns locally · no account
First run downloads Pyodide from a CDN. Use Run code after editing.

Practice this lesson

12 questions with immediate explanations.

See all 12 answersStart practice
← enumerate & zipdates & times →