Python pathlib Module
In this tutorial, we'll use pathlib to work with file and folder paths, check files, create folders, and read or write content.
The pathlib module represents filesystem paths as objects. It provides a readable, cross-platform alternative to manually joining path strings.
pathlib is part of Python's standard library:
from pathlib import Path
Creating Path objects
Create a path without opening or creating the file:
from pathlib import Path
report = Path("reports") / "2026" / "july.csv"
print(report)
On macOS and Linux the output is:
reports/2026/july.csv
The / operator joins path components using the correct separator for the operating system.
root = Path("data")
filename = "scores.json"
path = root / filename
This is clearer and safer than building paths with string concatenation.
Relative and absolute paths
A relative path is interpreted from the program's current working directory:
from pathlib import Path
path = Path("notes.txt")
print(path.is_absolute())
print(Path.cwd())
Resolve a path to an absolute path with .resolve():
absolute_path = path.resolve()
print(absolute_path)
.resolve() does not make a relative path portable between machines. Avoid hard-coding paths such as /Users/name/project or C:\\Users\\name\\project when your program needs to run elsewhere.
Inspecting path components
Path objects expose useful attributes:
from pathlib import Path
path = Path("reports/2026/july.csv")
print(path.name)
print(path.stem)
print(path.suffix)
print(path.parent)
print(path.parts)
Output on a POSIX system:
july.csv
july
.csv
reports/2026
('reports', '2026', 'july.csv')
For a name such as archive.tar.gz, .suffix is .gz and .suffixes is ['.tar', '.gz'].
Changing a name or extension
Path methods return new objects; they do not rename files on disk unless the method explicitly performs a filesystem operation.
from pathlib import Path
original = Path("reports/july.csv")
print(original.with_suffix(".json"))
print(original.with_name("august.csv"))
reports/july.json
reports/august.csv
Checking whether paths exist
Use these methods when your logic depends on the filesystem:
from pathlib import Path
path = Path("settings.json")
print(path.exists())
print(path.is_file())
print(path.is_dir())
The filesystem can change between a check and the next operation. When reading a file, you should still be prepared to handle FileNotFoundError or PermissionError.
Creating directories
Create one directory with .mkdir():
from pathlib import Path
Path("output").mkdir(exist_ok=True)
Create missing parent directories as well:
Path("output/reports/2026").mkdir(parents=True, exist_ok=True)
parents=Truecreates missing intermediate directories.exist_ok=Trueavoids an error when the directory already exists.
Do not use exist_ok=True automatically when an existing directory would indicate a genuine problem.
Reading and writing text
For small text files, Path has convenient methods:
from pathlib import Path
path = Path("message.txt")
path.write_text("Hello from pathlib\n", encoding="utf-8")
content = path.read_text(encoding="utf-8")
print(content)
Specify the encoding rather than relying on an operating-system default.
These methods read or write the whole file. For large files or line-by-line processing, use open():
from pathlib import Path
path = Path("large-log.txt")
with path.open("r", encoding="utf-8") as file:
for line in file:
print(line.rstrip())
Working with bytes
Use .read_bytes() and .write_bytes() for binary data:
from pathlib import Path
source = Path("image.png")
data = source.read_bytes()
Path("image-copy.png").write_bytes(data)
For large binary files, stream the data instead of loading the whole file into memory.
Finding files with glob
.glob() searches from a directory:
from pathlib import Path
project = Path(".")
for path in project.glob("*.py"):
print(path)
Use .rglob() or the ** pattern to search recursively:
for path in project.rglob("*.py"):
print(path)
The order returned by the filesystem is not guaranteed. Sort when order matters:
python_files = sorted(project.rglob("*.py"))
Iterating over a directory
.iterdir() returns the immediate contents of a directory:
from pathlib import Path
for item in Path(".").iterdir():
kind = "directory" if item.is_dir() else "file"
print(item.name, kind)
It does not search subdirectories recursively.
Moving, renaming, and deleting
These methods change the filesystem:
from pathlib import Path
draft = Path("draft.txt")
published = Path("published.txt")
draft.rename(published)
Delete a file with .unlink() and an empty directory with .rmdir():
published.unlink(missing_ok=True)
Path("empty-folder").rmdir()
Use destructive methods carefully. .rmdir() deliberately refuses to remove a non-empty directory.
A practical example: group files by extension
from collections import defaultdict
from pathlib import Path
def group_by_extension(folder):
groups = defaultdict(list)
for path in Path(folder).iterdir():
if path.is_file():
suffix = path.suffix.lower() or "[no extension]"
groups[suffix].append(path.name)
return {
suffix: sorted(names)
for suffix, names in groups.items()
}
This example keeps Path objects while inspecting files and converts to names only for the final result.
Common pathlib mistakes
- Converting every
Pathto a string too early. - Joining components with
+instead of/. - Assuming a relative path is relative to the Python file rather than the current working directory.
- Omitting an explicit text encoding.
- Assuming
.glob()returns results in alphabetical order. - Using
.exists()as a substitute for handling filesystem errors. - Forgetting that methods such as
.unlink()and.rename()change real files.
Practice
1. Build reports/2026/summary.txt from three separate values. 2. Return a file's name, stem, and lowercase suffix. 3. Find every .py file below a project directory. 4. Read a UTF-8 text file and count its non-empty lines. 5. Create an output directory and change a path's extension to .json.
Continue with the [interactive Python coding exercises](/codingexercises), including the premium Pathlib practice pack.