🐍 Python Course · Stage 3 · Lesson 30 of 89 · pathlib — File Paths
Course Overview →

🔵 Read Beginner and Intermediate tabs. The Expert tab has the internals — worth reading once you finish Stage 3.

← os — OS Interface json — Data Exchange →
thecodex.expert · The Codex Family of Knowledge
Python stdlib

Python pathlib — Object-Oriented Filesystem Paths module

Path objects that think — join with /, read files directly, glob recursively, no more string concatenation.

Python 3.13 Standard Library Last verified:
Canonical Definition

The Python pathlib module provides Path objects that represent filesystem paths as objects — supporting path joining with the / operator, direct file reading and writing, glob matching, and portable handling of platform path differences.

🟩 Beginner

The clean way to work with file paths

What you'll learn: Path objects, the / operator for joining paths, and the read_text/write_text shortcuts. By the end, pathlib will replace os.path in all your new code.

How to read this tab: Open the REPL and try: from pathlib import Path; p = Path("."); print(list(p.iterdir())). Then try p / "subfolder" / "file.txt". Feel how natural the / operator is.

⏱ 25 min 📄 2 sections 🔶 Prerequisite: File I/O
One sentence

pathlib turns file paths from plain strings into smart objects — you can join them with /, ask them questions (.exists(), .is_file()), read and write files directly, and never worry about \\ vs / again.

The / operator is pathlib's killer feature. Path("home") / "users" / "priya" / "documents" builds the path correctly on any OS. On Windows it uses backslashes, on macOS/Linux it uses forward slashes. You never have to think about separators again.

Path basics

Pythonpathlib_basics.py
from pathlib import Path

# Create a Path object
p = Path("/home/priya/data/report.csv")
home = Path.home()              # current user's home directory
cwd  = Path.cwd()               # current working directory

# Join paths with / operator
config = home / "projects" / "myapp" / "config.json"
print(config)   # /home/priya/projects/myapp/config.json

# Path components
print(p.name)       # report.csv
print(p.stem)       # report
print(p.suffix)     # .csv
print(p.parent)     # /home/priya/data
print(p.parts)      # ('/', 'home', 'priya', 'data', 'report.csv')

# Test what the path is
print(p.exists())   # True/False
print(p.is_file())  # True if file
print(p.is_dir())   # True if directory
💡

read_text() and write_text() are one-liners. For simple file reads/writes where you just need the whole content: text = Path("file.txt").read_text(encoding="utf-8"). No with statement, no f.read(), no close(). For large files that need streaming, use open() with a with block as usual.

Reading and writing files

Pythonpathlib_io.py
from pathlib import Path

# Read entire file as string
text = Path("readme.md").read_text(encoding="utf-8")

# Read as bytes
data = Path("image.png").read_bytes()

# Write text (creates file, overwrites if exists)
Path("output.txt").write_text("Hello, World!\n", encoding="utf-8")

# Write bytes
Path("data.bin").write_bytes(b"\x00\x01\x02")

# For line-by-line or large files, use open()
with Path("large.csv").open("r", encoding="utf-8") as f:
    for line in f:
        process(line.strip())
💡

rglob() replaces os.walk(). Finding all .py files: list(Path(".").rglob("*.py")). Finding all .csv files in a specific directory: list(Path("data").glob("*.csv")). glob() searches in the current directory; rglob() searches recursively. Both return generators — wrap in list() to materialise all results.

Listing and searching

Pythonpathlib_glob.py
from pathlib import Path

base = Path("/home/priya/projects")

# List immediate children
for item in base.iterdir():
    print(item)

# Glob — find files matching a pattern
for py_file in base.glob("*.py"):           # direct children only
    print(py_file)

for py_file in base.rglob("*.py"):          # recursive — all descendants
    print(py_file)

# Find all CSV files modified recently
import time
cutoff = time.time() - 86400   # 24 hours ago
recent = [p for p in base.rglob("*.csv")
          if p.stat().st_mtime > cutoff]

# Create directories
(base / "new_dir" / "subdir").mkdir(parents=True, exist_ok=True)

# Rename and delete
old = Path("old_name.txt")
new = old.with_name("new_name.txt")   # same directory, new name
old.rename(new)

Path("unwanted.txt").unlink(missing_ok=True)  # delete file, no error if missing

✅ Beginner tab complete — check your understanding

  • I can build paths with the / operator: Path("data") / "users" / "profile.json"
  • I can check existence with p.exists(), p.is_file(), p.is_dir()
  • I can read a file in one line: Path("file.txt").read_text(encoding="utf-8")
  • I can write a file in one line: Path("file.txt").write_text("content", encoding="utf-8")

Continue to json →

🔵 Intermediate

Listing, searching, and resolving paths

What you'll learn: iterdir() for listing a directory. glob() and rglob() for pattern-based search. resolve() for absolute paths. relative_to() for computing relative paths.

How to read this tab: Try Path(".").rglob("*.py") to find all Python files in your project recursively. This replaces a complex os.walk() loop with one elegant line.

⏱ 25 min 📄 2 sections 🔶 Prerequisite: Beginner tab
📎

pathlib for new code, os.path for existing code. Both are valid but pathlib is the modern standard. A Path object carries more information than a string and has methods that read naturally. The one limitation: some older libraries only accept strings — use str(path) or os.fspath(path) to convert.

pathlib vs os.path

Both do the same job. pathlib uses an OOP interface — a Path is an object with methods. os.path uses standalone functions on strings. Prefer pathlib for new code: it is more readable, composable (the / operator), and the Path object carries type information. Use os when you need environment variables, permissions, symlinks, or process-related calls that pathlib does not cover. Path objects can be passed to most functions that accept string paths — they implement __fspath__() (PEP 519).

Pythonpathlib_vs_os.py
# os.path style
import os
path = os.path.join(os.path.expanduser("~"), "projects", "app", "config.json")
if os.path.exists(path) and os.path.isfile(path):
    with open(path) as f:
        data = f.read()

# pathlib style — same thing
from pathlib import Path
path = Path.home() / "projects" / "app" / "config.json"
if path.exists() and path.is_file():
    data = path.read_text()

# Converting between them
import os
p = Path("/home/priya/data.csv")
as_string = str(p)         # "/home/priya/data.csv"
as_string = os.fspath(p)   # same — uses __fspath__

# Path arithmetic
p = Path("report.csv")
json_version = p.with_suffix(".json")   # report.json
backup = p.with_name(f"backup_{p.name}")  # backup_report.csv
📎

resolve() gives you the true absolute path. It resolves symlinks and relative path components (.. and .). path.relative_to("/home/priya") strips the prefix and gives you the part of the path relative to that base. Useful when you need to mirror a directory structure.

Resolving and relative paths

Pythonpathlib_resolve.py
from pathlib import Path

# relative_to — compute relative path
base = Path("/home/priya/projects")
file = Path("/home/priya/projects/app/main.py")
rel  = file.relative_to(base)
print(rel)   # app/main.py

# resolve — canonicalise (follows symlinks, resolves ..)
p = Path("../data/../data/file.txt")
print(p.resolve())   # absolute path with symlinks resolved

# is_relative_to (Python 3.9+)
print(file.is_relative_to(base))   # True

# samefile — same file even through symlinks
p1 = Path("/tmp/link")
p2 = Path("/home/priya/original")
print(p1.samefile(p2))   # True if they point to same inode
Commonly confused
Path("~") does not expand the tilde. Path("~/documents") gives a path starting with literal ~. Use Path("~/documents").expanduser() or Path.home() / "documents" to get the actual home directory.
path.unlink() only removes files, not directories. To remove a directory, use path.rmdir() (empty only) or shutil.rmtree(path) (with contents). unlink() on a directory raises IsADirectoryError.
rglob("*.py") and glob("**/*.py") are equivalent but not the same call. path.rglob("*.py") is shorthand for path.glob("**/*.py"). Both search recursively.
How this connects

✅ Intermediate tab complete — check your understanding

  • I can list all Python files recursively with list(Path(".").rglob("*.py"))
  • I can use glob() for non-recursive search with wildcards
  • I can use resolve() to get the absolute path and follow symlinks
  • I can compute a relative path with path.relative_to(base)

Continue to json →

🔴 Expert

PEP 428, the Path hierarchy, and os.fspath()

What you'll learn: The pathlib design philosophy (PEP 428). The Path class hierarchy (PurePath → Path → PosixPath/WindowsPath). PEP 519 and os.fspath() for accepting both str and Path objects.

How to read this tab: Read when writing library code that should accept both str and Path arguments for file paths.

⏱ 15 min 📄 2 sections 🔶 Prerequisite: After Stage 3
📎

PurePath vs Path: PurePath provides path manipulation without any I/O — useful on systems where the path might not exist (building paths for remote systems, testing). Path is PurePath plus actual I/O operations. On import, Path resolves to PosixPath on Unix and WindowsPath on Windows automatically.

PEP 428 and the Path hierarchy

PEP 428 (Python 3.4, 2013) introduced pathlib. The class hierarchy: PurePath (no I/O, just path manipulation) → PurePosixPath / PureWindowsPath; Path(PurePath) (adds I/O) → PosixPath / WindowsPath. Path() automatically instantiates the platform-appropriate concrete class. PurePath objects are useful for path manipulation in cross-platform code (e.g. building Windows paths on a Linux machine) without requiring the path to exist.

PEP 519 — os.fspath() and __fspath__

PEP 519 (Python 3.6, 2016) defined the "file system path protocol." Any object implementing __fspath__() that returns a str or bytes is a "path-like object." os.fspath(p) calls p.__fspath__(). All Python stdlib functions accepting file paths accept path-like objects. This is why open(Path("file.txt")) works.

✅ Expert tab complete

  • I know pathlib provides both PurePath (no I/O) and Path (with I/O) classes
  • I can use os.fspath() or str() to convert a Path to a string for APIs that don't accept Path objects

Continue to json →

Sources

1
Python Standard Library — pathlib. docs.python.org/3/library/pathlib.html.
2
PEP 428 — The pathlib module. peps.python.org/pep-0428/.
3
PEP 519 — Adding a file system path protocol. peps.python.org/pep-0519/.
Source confidence: High Last verified: Primary source: docs.python.org/3/library/pathlib.html

Sources

1
Python Standard Library — pathlib. docs.python.org/3/library/pathlib.html.
2
PEP 428 — The pathlib module. peps.python.org/pep-0428/.
3
PEP 519 — File system path protocol. peps.python.org/pep-0519/.