Stop Using os.path: Why pathlib Will Change How You Handle Files
Learn why pathlib is the modern Python way to handle file paths: more readable, cross-platform, and less error-prone than os.path. Includes practical examples and a real downloads organizer script.
Stop Using os.path: Why pathlib Will Change How You Handle Files in Python
I remember the exact moment I discovered pathlib. I was debugging a script that used os.path.join() with a mix of forward and backward slashes, wondering why my colleague's Windows machine kept crashing while my Linux system worked fine. Three hours later, I rewrote everything using pathlib, and I haven't touched os.path since.
Let me show you why.
What Makes pathlib Different?
The traditional way of handling file paths in Python looks like this:
import os
path = os.path.join("data", "images", "photo.jpg")
if os.path.exists(path):
with open(path, "r") as f:
content = f.read()
It works, but it's messy. You're juggling strings, remembering which function does what, and hoping your slashes are correct.
Enter pathlib. Instead of working with strings, you work with objects:
from pathlib import Path
path = Path("data/images/photo.jpg") # Slashes work on any OS
if path.exists():
content = path.read_text() # One method call instead of three
See the difference? The Path object handles operating system differences automatically. No more worrying about Windows vs Linux slashes.
Getting Started: Creating Path Objects
Creating a path is straightforward:
from pathlib import Path
# Current directory
current = Path(".")
# Home directory
home = Path.home()
# Absolute path
absolute = Path("/usr/local/bin")
# Relative path
relative = Path("docs/manual.pdf")
You can also combine paths cleanly:
base = Path("project")
config = base / "config" / "settings.json"
# Result: project/config/settings.json (on Linux/Mac)
# Result: project\config\settings.json (on Windows)
The / operator works as path joining. No more os.path.join() confusion.
Common File Operations Made Simple
Here's where pathlib really shines. Let me show you some everyday operations:
Reading and Writing Files
path = Path("notes.txt")
# Read entire file
content = path.read_text()
# Write to file
path.write_text("Hello, world!")
# Append to file
with path.open("a") as f:
f.write("More content")
No more with open() boilerplate for simple operations. Though for binary files, use read_bytes() and write_bytes():
image = Path("photo.png")
data = image.read_bytes()
Checking File Properties
path = Path("document.pdf")
print(path.exists()) # True or False
print(path.is_file()) # Is it a file?
print(path.is_dir()) # Is it a directory?
print(path.stat().st_size) # File size in bytes
print(path.suffix) # .pdf
print(path.stem) # document (without extension)
print(path.name) # document.pdf
Working with Directories
docs = Path("documents")
# List all files in directory
for file in docs.iterdir():
print(file.name)
# List only PDF files
for pdf in docs.glob("*.pdf"):
print(pdf)
# Recursive search
for all_files in docs.rglob("*"):
print(all_files)
rglob("*") is a personal favorite. It recursively finds everything inside a directory tree.
Creating and Deleting Files
path = Path("new_folder")
# Create directory (and parents if needed)
path.mkdir(exist_ok=True, parents=True)
# Create empty file
file = path / "example.txt"
file.touch()
# Delete file
file.unlink()
# Delete empty directory
path.rmdir()
# Delete directory and all contents
import shutil
shutil.rmtree(path)
Always use exist_ok=True with mkdir() unless you specifically want an error. Your future self will thank you.
Real Example: Organizing a Downloads Folder
Let me give you something practical. Here's a script that organizes your downloads by file type:
from pathlib import Path
import shutil
def organize_downloads(downloads_path="~/Downloads"):
downloads = Path(downloads_path).expanduser()
if not downloads.exists():
print("Downloads folder not found")
return
file_types = {
"Images": [".jpg", ".jpeg", ".png", ".gif"],
"Documents": [".pdf", ".docx", ".txt", ".xlsx"],
"Archives": [".zip", ".tar", ".gz"],
"Videos": [".mp4", ".avi", ".mkv"]
}
for file in downloads.iterdir():
if file.is_file():
moved = False
for folder, extensions in file_types.items():
if file.suffix.lower() in extensions:
target = downloads / folder
target.mkdir(exist_ok=True)
shutil.move(str(file), str(target / file.name))
print(f"Moved {file.name} to {folder}")
moved = True
break
if not moved:
other = downloads / "Other"
other.mkdir(exist_ok=True)
shutil.move(str(file), str(other / file.name))
print(f"Moved {file.name} to Other")
if __name__ == "__main__":
organize_downloads()
Run this once a week, and your Downloads folder stays clean.
Platform Independence: The Hidden Superpower
Here's something that bit me hard in production. Consider this code:
# Bad: breaks on Windows
path = "data/images/" + filename
# Bad: might break with different OS
path = os.path.join("data", "images", filename)
# Good: works everywhere
path = Path("data/images") / filename
The last version handles everything. Forward slashes, backward slashes, mix of both — pathlib normalizes it all. When I deployed a Flask app that used pathlib, I never got another "file not found" error from Windows users.
Path Manipulation Without Fear
Need to change a file extension? Easy:
report = Path("report.docx")
new_report = report.with_suffix(".pdf")
# Result: report.pdf
Need to go up a directory?
current = Path("/home/user/project/src/file.py")
parent = current.parent # /home/user/project/src
grandparent = current.parent.parent # /home/user/project
Need absolute paths?
relative = Path("docs/manual.pdf")
absolute = relative.absolute() # /home/user/docs/manual.pdf
When Not to Use pathlib
I'll be honest — pathlib isn't perfect for everything. Here's when I still use os.path:
- Extreme performance scenarios:
os.pathcan be slightly faster for millions of operations - Legacy code maintenance: Don't refactor working code just for fun
- Very simple scripts: If you're just checking if one file exists,
os.path.exists()is fine
But for 95% of file operations, pathlib is the better choice.
Final Thoughts
When I switched to pathlib at PythonSkillset, our error rates for file-related bugs dropped significantly. The code became more readable, more reliable, and easier to maintain.
Start small. Next time you write a script that touches files, import Path instead of os.path. After a few scripts, you'll wonder why you ever used strings for file paths.
The only cost? Adding one import line. The benefit? File operations that actually make sense.
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.