Python

Python pathlib: Modern File System Operations

Learn how to use Python's pathlib module to handle file paths, read and write files, and traverse directories with clean, object-oriented code instead of string manipulation.

August 2026 6 min read 9 views 0 hearts

Python's pathlib: A Modern Approach to File System Operations

Remember the old days of wrestling with os.path.join() and worrying about forward slashes on Windows? I certainly do. It was a mess of string concatenation, separator checks, and code that looked like alphabet soup. Then pathlib came along in Python 3.4, and everything changed.

Let me show you why this module has become my go-to tool for every file system task.

The Core Idea: Paths as Objects

Instead of treating file paths as strings, pathlib treats them as objects with methods and properties. This small shift makes your code cleaner, more readable, and less error-prone.

Here's the old way:

import os

file_path = os.path.join("data", "reports", "2024", "quarterly.csv")
print(os.path.dirname(file_path))
print(os.path.basename(file_path))
print(os.path.splitext(file_path)[0])

And here's the pathlib way:

from pathlib import Path

file_path = Path("data") / "reports" / "2024" / "quarterly.csv"
print(file_path.parent)
print(file_path.name)
print(file_path.stem)

Notice the / operator? It works like joining path components. No more worrying about slashes or OS differences.

Reading and Writing Files

One of the biggest time-savers for me at PythonSkillset has been how pathlib simplifies file I/O. Let me walk you through a real scenario we faced recently.

We needed to process a batch of configuration files. Here's the before and after:

# The messy way
import os

config_dir = "./config_files"
for filename in os.listdir(config_dir):
    if filename.endswith(".json"):
        filepath = os.path.join(config_dir, filename)
        with open(filepath, "r") as f:
            data = json.load(f)
        # process data
# The clean way
from pathlib import Path

config_dir = Path("./config_files")
for filepath in config_dir.glob("*.json"):
    data = json.loads(filepath.read_text())
    # process data

The read_text() method reads the entire file as a string. There's also read_bytes() for binary files, write_text(), and write_bytes(). One line instead of three.

Walking Through Directories

When I needed to restructure our documentation at PythonSkillset, pathlib made tree traversal almost enjoyable:

from pathlib import Path

docs_dir = Path("./documentation")
for filepath in docs_dir.rglob("*.md"):
    print(f"Found: {filepath.relative_to(docs_dir)}")
    print(f"Size: {filepath.stat().st_size} bytes")

    # Rename .md files to .txt for a draft review
    if "draft" in filepath.stem:
        new_path = filepath.with_suffix(".txt")
        filepath.rename(new_path)
        print(f"Renamed to: {new_path}")

The rglob("*") method recursively finds all files. The relative_to() method gives you the path relative to a base directory. And with_suffix() safely changes file extensions.

Common Operations Made Simple

Here are some everyday tasks that pathlib handles beautifully:

Creating Nested Directories

from pathlib import Path

new_project = Path("./projects/my_app/src")
new_project.mkdir(parents=True, exist_ok=True)

One call with parents=True creates the entire chain. No more looping through parent directories.

Checking File Properties

from pathlib import Path

file = Path("./data.csv")
if file.exists() and file.is_file() and file.stat().st_size > 0:
    print(f"File {file.name} exists and is non-empty")

Working with User Directories

from pathlib import Path

home = Path.home()
desktop = home / "Desktop"
downloads = home / "Downloads"

No more os.path.expanduser("~") or os.environ["HOME"].

A Real-World Example: Cleaning Up Cache Files

At PythonSkillset, we run weekly cleanup scripts. Here's how pathlib handles it gracefully:

from pathlib import Path
import shutil

def clean_cache(cache_dir=".cache"):
    cache_path = Path(cache_dir)

    if not cache_path.exists():
        print(f"No cache directory found at {cache_path}")
        return

    # Remove all .tmp files older than 7 days
    for tmp_file in cache_path.rglob("*.tmp"):
        age_days = (pathlib.Path.now() - tmp_file.stat().st_mtime) / 86400
        if age_days > 7:
            tmp_file.unlink()
            print(f"Removed: {tmp_file}")

    # Remove empty directories
    for dir_path in sorted(cache_path.rglob("*"), reverse=True):
        if dir_path.is_dir() and not any(dir_path.iterdir()):
            dir_path.rmdir()
            print(f"Removed empty directory: {dir_path}")

    print("Cache cleanup complete")

Notice how rglob("*") gives us all items, then we check each one's properties. The iterdir() method checks for directory contents. And rmdir() only removes empty directories safely.

The Pathlib Mindset

Once you start thinking in pathlib, you'll find yourself reaching for it instinctively. The key insight is this: paths aren't strings to manipulate, they're objects with inherent properties and behaviors.

When you see Path("config") / "settings.json", you're not building a string. You're constructing a path object that knows about your operating system, can verify its existence, read its contents, and move itself around.

I've been using pathlib daily for years at PythonSkillset, and it's one of those rare tools that makes me smile every time I use it. Give it a week in your next project, and I bet you'll feel the same way.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.