Skip to Content
Course content

233: Pathlib for Filesystem Paths

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was working on a script this morning to clean up some legacy server logs, and I found myself staring at a block of code that looked like a mess of string concatenations. I was using the old os.path module, and it just felt... clunky. Let me show you what I was dealing with.

The String Glue Problem

import os

log_dir = "logs/archive/2023"
filename = "server_error.log"
full_path = os.path.join(log_dir, filename)

if os.path.exists(full_path):
    print(f"Found it at {full_path}")
    # Now I want the filename without the extension...
    base_name = os.path.splitext(os.path.basename(full_path))[0]
    print(f"The log base name is {base_name}")

It works, sure. But look at that os.path.splitext(os.path.basename(full_path))[0] line. It's a nested nightmare. I'm treating paths as strings, and that's where the friction comes from. Paths aren't really strings; they're hierarchical objects. So, I decided to rip this out and use pathlib instead.

Thinking in Objects

The first thing I did was swap the import. With pathlib, we use the Path class. I'll try recreating that same logic and see if it feels more intuitive.

from pathlib import Path

# I'll wrap the string in a Path object immediately
log_dir = Path("logs/archive/2023")
filename = "server_error.log"

# Here is the magic part: the slash operator
full_path = log_dir / filename

print(f"Path is now: {full_path}")

Wait, a forward slash for joining paths? At first, I thought that was a mistake, but it's actually an overloaded operator. pathlib uses it to represent the filesystem hierarchy. It's much cleaner than os.path.join(). Now, let's look at that "nested nightmare" from before.

# Instead of splitext and basename...
print(f"The log base name is {full_path.stem}")
print(f"The extension is {full_path.suffix}")

That's a massive improvement. .stem gives me the filename without the extension, and .suffix gives me the extension. No more indexing into tuples returned by a function.

Hunting for Specific Files

Now, the actual goal of my script was to find all .log files in that directory and move them. In the old days, I'd use os.listdir() and then write an if file.endswith(".log") block. Let's see if pathlib has a better way to "hunt."

# I'll try using .glob()
for log_file in log_dir.glob("*.log"):
    print(f"Processing {log_file.name}...")

This is significantly more expressive. glob() returns a generator of Path objects, meaning I can immediately call .stem or .unlink() (to delete) on each item without having to re-wrap them in Path(). If I wanted to search recursively through all subfolders, I'd just change the pattern to "**/*.log". Simple.

Cleaning up the Boilerplate

One last thing. I needed to create a backup directory if it didn't exist. Usually, that's a try/except block or an if not os.path.exists() check. I wondered if Path could handle the "create if missing" logic in one go.

backup_dir = Path("logs/backup")
backup_dir.mkdir(parents=True, exist_ok=True)

The parents=True argument creates any missing parent directories in the path, and exist_ok=True prevents Python from throwing an error if the folder is already there. It turns three lines of defensive code into one. When you combine these—the slash operator, the .stem property, and the .glob() method—your filesystem code stops looking like string manipulation and starts looking like actual logic.




📋 Practical Task

Log File Organizer Utility

You have a directory called system_logs containing various files: app.log, error.log, setup.txt, and debug.log. Write a script using pathlib that does the following:

  • Creates a directory named log_archives (ensure it doesn't crash if the directory already exists).
  • Iterates through the system_logs directory and finds all files ending in .log.
  • For each log file found, "moves" it (you can simulate this by printing a message) to the log_archives folder, but rename the file by prefixing the filename with the word archived_ (e.g., app.log becomes archived_app.log).

Requirement: Use the / operator for path joining and the .name or .stem properties for renaming.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.