For years, Python developers used the os.path module for file system operations, working with paths as strings. Python 3.4 introduced pathlib, a modern object-oriented approach that makes path handling more intuitive and readable.
In this post, I’ll show you how pathlib improves upon traditional os.path patterns, helping you write cleaner path-handling code.
The Traditional Approach: os.path#
The os.path module represents paths as strings. While functional, this approach requires remembering various function names and often leads to verbose code:
import os
file_path = os.path.join('directory', 'subdirectory', 'file.txt')
parent_folder = os.path.dirname(file_path)
filename = os.path.basename(file_path)
extension = os.path.splitext(file_path)[1]
The Modern Approach: pathlib#
Introduced in Python 3.4, pathlib provides an object-oriented interface where paths are objects with intuitive methods and properties. This makes code more discoverable and easier to read.
Common Patterns Improved#
Let me show you how pathlib simplifies common path operations.
Working with Paths & File Attributes#
Traditional approach:
import os
file_path = os.path.join('directory', 'subdirectory', 'file.txt')
file_exists = os.path.isfile(file_path)
parent_folder = os.path.dirname(file_path) # directory/subdirectory
filename = os.path.basename(file_path) # file.txt
extension = os.path.splitext(file_path)[1] # .txt
Modern approach with pathlib:
from pathlib import Path
file_path = Path('directory') / 'subdirectory' / 'file.txt'
file_exists = file_path.is_file()
parent_folder = file_path.parent # directory/subdirectory
filename = file_path.name # file.txt
extension = file_path.suffix # .txt
Notice how pathlib uses intuitive properties like .parent, .name, and .suffix instead of separate functions. The / operator for joining paths is also more readable than os.path.join().
Reading Files#
Traditional approach:
import os
file_path = os.path.join('directory', 'file.txt')
with open(file_path, 'r') as file:
content = file.read()
Modern approach with pathlib:
from pathlib import Path
file_path = Path('directory') / 'file.txt'
content = file_path.read_text()
The read_text() method eliminates boilerplate - no need to manually open and close files for simple read operations.
Listing All Files in a Directory#
Traditional approach:
import os
directory = 'some_directory'
files = [
os.path.join(directory, f)
for f in os.listdir(directory)
if os.path.isfile(os.path.join(directory, f))
]
Modern approach with pathlib:
from pathlib import Path
directory = Path('some_directory')
files = [f for f in directory.iterdir() if f.is_file()]
The iterdir() method returns path objects, making it easy to chain operations. Much cleaner than juggling strings with os.listdir().
Compatibility#
Python 3.6 introduced os.PathLike, which standardizes the representation of file system paths. This means most built-in functions (like open()) accept both strings and Path objects interchangeably:
import os
from pathlib import Path
# both of the following work with open(), although different types
file_path = os.path.join('directory', 'subdirectory', 'file.txt')
file_path = Path('directory') / 'subdirectory' / 'file.txt'
with open(file_path, 'r') as file:
content = file.read()
If you need to work with older APIs that only accept strings, simply use str() to convert a Path object.
Why I Recommend pathlib#
pathlib brings several advantages to modern Python code:
- More readable: The
/operator and property-based access make intent clear - Less error-prone: Methods are discoverable through autocomplete
- Less boilerplate: Built-in methods like
read_text()eliminate common patterns - Object-oriented: Easier to compose and pass around than strings
While os.path still works perfectly fine (and may be necessary for legacy codebases or performance-critical scenarios), I recommend using pathlib for new code. It’s more Pythonic and easier to maintain.
Bonus Tip#
If you like the pathlib interface, check out
cloudpathlib
- it extends the same path-like interface to cloud storage (AWS S3, Azure Blob Storage, etc.):
from cloudpathlib import CloudPath
with CloudPath("s3://bucket/filename.txt").open("w+") as f:
f.write("Send my changes to the cloud!")