Pathlib
Learn how to work with file paths in Python the modern way using the pathlib module.
Pathlib
Before pathlib, working with file paths in Python meant using os.path — a collection of functions that took strings and returned strings. It worked but was clunky:
import os
path = os.path.join("home", "ali", "documents", "report.txt")
filename = os.path.basename(path)
directory = os.path.dirname(path)Python 3.4 introduced pathlib — a modern, object-oriented way to work with paths. Instead of string functions, you get a Path object with methods and properties that make path operations clean and readable.
from pathlib import Path
path = Path("home") / "ali" / "documents" / "report.txt"
filename = path.name
directory = path.parentMuch cleaner. pathlib is now the recommended standard for path handling in modern Python.
Creating a Path
from pathlib import Path
# from a string
p = Path("documents/report.txt")
# current directory
current = Path(".")
print(current) # .
# home directory
home = Path.home()
print(home) # /home/ali (or C:\Users\Ali on Windows)
# absolute path of current directory
cwd = Path.cwd()
print(cwd) # /home/ali/projectsThe / operator — building paths
The most satisfying thing about pathlib — you build paths with / just like you write them:
from pathlib import Path
base = Path("/home/ali")
documents = base / "documents"
report = base / "documents" / "report.txt"
nested = base / "projects" / "python" / "app" / "main.py"
print(documents) # /home/ali/documents
print(report) # /home/ali/documents/report.txtThis works on all operating systems. On Windows it produces \ separators automatically — you never have to think about it.
Path properties
A Path object has properties that give you the different parts of a path without any string slicing:
from pathlib import Path
p = Path("/home/ali/documents/report.pdf")
print(p.name) # report.pdf — filename with extension
print(p.stem) # report — filename without extension
print(p.suffix) # .pdf — the extension
print(p.suffixes) # ['.pdf'] — list of all extensions
print(p.parent) # /home/ali/documents
print(p.parents[0]) # /home/ali/documents
print(p.parents[1]) # /home/ali
print(p.parts) # ('/', 'home', 'ali', 'documents', 'report.pdf')
print(p.root) # /
print(p.anchor) # /Multiple extensions:
p = Path("archive.tar.gz")
print(p.name) # archive.tar.gz
print(p.stem) # archive.tar
print(p.suffix) # .gz
print(p.suffixes) # ['.tar', '.gz']Checking what exists
from pathlib import Path
p = Path("documents/report.txt")
print(p.exists()) # True or False — does it exist at all?
print(p.is_file()) # True if it exists and is a file
print(p.is_dir()) # True if it exists and is a directoryA real use — check before opening:
config = Path("config.json")
if config.exists():
content = config.read_text(encoding="utf-8")
print(content)
else:
print("Config file not found. Using defaults.")Reading and writing files
pathlib has its own read and write methods — no need to use open() for simple cases:
read_text() and write_text()
from pathlib import Path
p = Path("notes.txt")
# write
p.write_text("Hello, world!\nThis is a note.", encoding="utf-8")
# read
content = p.read_text(encoding="utf-8")
print(content)read_bytes() and write_bytes()
# copy a binary file
source = Path("photo.jpg")
destination = Path("photo_copy.jpg")
destination.write_bytes(source.read_bytes())write_text() and write_bytes() always overwrite the file completely. For appending or more control, use open() with a with statement — pathlib works perfectly with open() too:
with open(Path("log.txt"), "a", encoding="utf-8") as f:
f.write("New log entry\n")Creating directories
from pathlib import Path
# create one directory
Path("new_folder").mkdir()
# create nested directories
Path("a/b/c").mkdir(parents=True)
# do not raise an error if the directory already exists
Path("new_folder").mkdir(parents=True, exist_ok=True)Always use exist_ok=True in real code — otherwise you get an error if the folder already exists.
Listing directory contents
iterdir() — list everything in a directory
from pathlib import Path
folder = Path(".")
for item in folder.iterdir():
print(item)Output:
main.py
utils.py
documents
data.json
venvFilter only files or only directories:
for item in folder.iterdir():
if item.is_file():
print(f"File: {item.name}")
elif item.is_dir():
print(f"Dir: {item.name}")glob() — find files matching a pattern
glob() searches for files matching a pattern. * matches anything within one directory level:
from pathlib import Path
folder = Path(".")
# all Python files in current directory
for py_file in folder.glob("*.py"):
print(py_file.name)
# all JSON files in current directory
for json_file in folder.glob("*.json"):
print(json_file.name)
# all files with any name but .txt extension
for txt_file in folder.glob("*.txt"):
print(txt_file)rglob() — recursive glob
rglob() searches through all subdirectories too. rglob("*.py") finds every Python file in the entire folder tree:
from pathlib import Path
project = Path(".")
# find every Python file in the project
for py_file in project.rglob("*.py"):
print(py_file)Output:
main.py
utils.py
tests/test_main.py
tests/test_utils.py
src/app/routes.py
src/app/models.pyRenaming and moving files
from pathlib import Path
p = Path("old_name.txt")
# rename
p.rename("new_name.txt")
# move to another directory
p = Path("report.txt")
p.rename(Path("archive") / "report.txt")Deleting files and directories
from pathlib import Path
# delete a file
Path("temp.txt").unlink()
# delete a file — no error if it does not exist (Python 3.8+)
Path("temp.txt").unlink(missing_ok=True)
# delete an empty directory
Path("empty_folder").rmdir()pathlib has no built-in method to delete a non-empty directory. Use shutil.rmtree() for that:
import shutil
shutil.rmtree(Path("folder_with_files"))Converting between Path and string
Sometimes you need a plain string — for example, passing a path to a library that does not support pathlib:
from pathlib import Path
p = Path("/home/ali/report.txt")
# Path to string
print(str(p)) # /home/ali/report.txt
# string to Path
s = "/home/ali/report.txt"
p = Path(s)Most modern Python libraries accept Path objects directly. Only older libraries need you to convert to string with str(p).
Absolute vs relative paths
from pathlib import Path
# relative — relative to where the script is run from
relative = Path("documents/report.txt")
# convert to absolute
absolute = relative.resolve()
print(absolute) # /home/ali/projects/documents/report.txt
# check if absolute
print(relative.is_absolute()) # False
print(absolute.is_absolute()) # Truefile — the path of the current script
Every Python file has a __file__ variable containing its own path. Combined with pathlib, this is how you reliably build paths relative to your script — no matter where the script is run from:
from pathlib import Path
# the directory this script lives in
BASE_DIR = Path(__file__).parent
# build paths relative to the script
data_file = BASE_DIR / "data" / "users.json"
config = BASE_DIR / "config.json"
print(data_file) # /home/ali/projects/data/users.jsonThis is far more reliable than Path("data/users.json") which is relative to wherever you ran the script from.
Path(__file__).parent is one of the most useful patterns in Python. Use it any time you need to reference a file relative to your script's location — config files, data files, templates, anything.
A real example
A script that organizes files in a folder by extension:
from pathlib import Path
import shutil
def organize_folder(folder_path):
folder = Path(folder_path)
if not folder.exists():
print(f"Folder {folder_path} does not exist.")
return
# map extensions to folder names
categories = {
".pdf": "PDFs",
".doc": "Documents",
".docx": "Documents",
".jpg": "Images",
".jpeg": "Images",
".png": "Images",
".mp3": "Audio",
".mp4": "Videos",
".py": "Python",
".json": "Data",
".csv": "Data",
}
moved = 0
for file in folder.iterdir():
if not file.is_file():
continue
category = categories.get(file.suffix.lower(), "Other")
destination = folder / category
destination.mkdir(exist_ok=True)
shutil.move(str(file), str(destination / file.name))
print(f"Moved {file.name} → {category}/")
moved += 1
print(f"\nDone. {moved} files organized.")
organize_folder("/home/ali/Downloads")Output:
Moved report.pdf → PDFs/
Moved photo.jpg → Images/
Moved notes.docx → Documents/
Moved data.csv → Data/
Moved script.py → Python/
Done. 5 files organized.pathlib vs os.path
| Task | os.path | pathlib |
|---|---|---|
| Join paths | os.path.join(a, b) | Path(a) / b |
| Get filename | os.path.basename(p) | p.name |
| Get directory | os.path.dirname(p) | p.parent |
| Get extension | os.path.splitext(p)[1] | p.suffix |
| Check exists | os.path.exists(p) | p.exists() |
| Is file | os.path.isfile(p) | p.is_file() |
| Is directory | os.path.isdir(p) | p.is_dir() |
| Make directory | os.makedirs(p) | p.mkdir(parents=True) |
| Absolute path | os.path.abspath(p) | p.resolve() |
Summary
| Concept | Example |
|---|---|
| Create a path | Path("folder/file.txt") |
Build with / | Path("home") / "ali" / "file.txt" |
| Filename | p.name |
| Name without extension | p.stem |
| Extension | p.suffix |
| Parent directory | p.parent |
| Check exists | p.exists() |
| Read text | p.read_text(encoding="utf-8") |
| Write text | p.write_text("content", encoding="utf-8") |
| Create directory | p.mkdir(parents=True, exist_ok=True) |
| List directory | p.iterdir() |
| Find by pattern | p.glob("*.py") |
| Find recursively | p.rglob("*.py") |
| Delete file | p.unlink(missing_ok=True) |
| Script-relative path | Path(__file__).parent / "data" |