Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
233: Pathlib for Filesystem Paths
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_logsdirectory 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_archivesfolder, but rename the file by prefixing the filename with the wordarchived_(e.g.,app.logbecomesarchived_app.log).
Requirement: Use the / operator for path joining and the .name or .stem properties for renaming.
There are no comments for now.