The pathlib module in the Python standard library provides an object-oriented way to work with file paths. Instead of joining strings with os.path.join and worrying about forward slashes versus backslashes, you create Path objects and use the / operator. The same code works on Windows, macOS, and Linux.
from pathlib import Path
desktop = Path.home() / "Desktop"
project = desktop / "my-project" / "main.py"
print(desktop) # /Users/maya/Desktop
print(project) # /Users/maya/Desktop/my-project/main.py
print(project.name) # main.py
print(project.suffix) # .pyPath.home() returns the user's home directory. The / operator joins path components. project.name returns the final component (main.py), and project.suffix returns the file extension.
Creating Path objects
You can create a Path from a string, from parts, or relative to the home or current directory. Choosing the right constructor depends on whether you already know the full path, want to build it piece by piece, or need a path that depends on where the script runs.
from pathlib import Path
print(Path("data/config.json")) # data/config.json
print(Path("reports") / "2026" / "summary.csv") # reports/2026/summary.csv
print(Path.home()) # /Users/maya
print(Path.cwd()) # /Users/maya/Dev/projectPath("data/config.json") creates a relative path. Path.home() and Path.cwd() create absolute paths. The / operator builds paths one segment at a time, which is cleaner than string concatenation.
Inspecting path properties
A Path object has several attributes for examining its parts.
from pathlib import Path
file = Path("/Users/maya/Documents/report.pdf")
print(file.name) # report.pdf
print(file.stem) # report
print(file.suffix) # .pdf
print(file.parent) # /Users/maya/Documents
print(file.parts) # ('/', 'Users', 'maya', 'Documents', 'report.pdf')| Attribute | Description | Example |
|---|---|---|
| .name | Final path component | report.pdf |
| .stem | Filename without extension | report |
| .suffix | File extension with dot | |
| .parent | Directory containing the file | /Users/maya/Documents |
| .parts | Tuple of all path components | ('/', 'Users', 'maya', 'Documents', 'report.pdf') |
These attributes let you derive related paths without string manipulation:
from pathlib import Path
file = Path("data/input.csv")
output = file.parent / (file.stem + "_processed" + file.suffix)
print(output) # data/input_processed.csvChecking if paths exist
Before reading or writing a file, it often helps to confirm the path is what you expect. Use .exists(), .is_file(), and .is_dir() to inspect paths without opening them or risking an exception.
from pathlib import Path
config = Path("config.json")
if config.exists():
print(f"{config} exists")
if config.is_file():
print(f"{config} is a file")
if config.is_dir():
print(f"{config} is a directory")These methods return True or False. They do not raise exceptions when the path is missing, so they are safe for conditional checks.
Reading and writing files
For simple file I/O, pathlib provides .read_text() and .write_text().
from pathlib import Path
log = Path("app.log")
log.write_text("2026-07-12 10:00:00 - Server started\n")
content = log.read_text()
print(content) # 2026-07-12 10:00:00 - Server started.write_text() creates the file if it does not exist and overwrites it if it does. .read_text() returns the entire file content as a string. For large files, open files with a context manager instead, to avoid loading everything into memory.
For bytes, use .read_bytes() and .write_bytes():
from pathlib import Path
data = Path("data.bin")
data.write_bytes(b"\x00\x01\x02")
print(data.read_bytes()) # b'\x00\x01\x02'Creating and removing directories
Use .mkdir() to create directories and .rmdir() to remove empty ones. Passing parents=True and exist_ok=True makes mkdir tolerant of missing intermediate folders and of directories that already exist.
from pathlib import Path
output = Path("output/2026/reports")
output.mkdir(parents=True, exist_ok=True)
(output / "summary.txt").write_text("Q3 Summary")mkdir(parents=True) creates all intermediate directories. exist_ok=True prevents an error if the directory already exists. Together, these flags let you write one line that ensures a directory path exists, no matter the current state.
Listing directory contents
Use .iterdir() to list entries in a directory.
from pathlib import Path
project = Path(".")
for entry in project.iterdir():
kind = "file" if entry.is_file() else "dir"
print(f"{kind}: {entry.name}")The loop prints one line per entry in the current directory, labeling each as a file or a directory, in whatever order the operating system returns them:
dir: src
dir: tests
file: README.md
file: pyproject.toml.iterdir() returns Path objects, so you can call .is_file(), .name, or any other Path method directly on each entry.
Pattern matching with glob
Use .glob() to find files matching a pattern.
from pathlib import Path
project = Path(".")
for py_file in project.glob("*.py"):
print(py_file.name)
for py_file in project.glob("**/*.py"):
print(py_file)The first loop only checks the current folder, so it prints one name. The second loop walks every subdirectory too, which is why main.py shows up again alongside files nested inside src and tests:
main.py
main.py
src/utils.py
src/models.py
tests/test_utils.pyglob(".py") matches Python files in the current directory. glob("**/.py") matches Python files in the current directory and all subdirectories recursively. The ** pattern means "any depth."
Common glob patterns:
| Pattern | Matches |
|---|---|
| *.txt | All .txt files in the current directory |
| **/*.json | All .json files recursively |
| data/**/backup | All directories named backup under data/ |
| report_2026*.csv | CSV files starting with report_2026 |
Building portable paths
Never hardcode \ or / in path strings. Use pathlib and the / operator instead.
from pathlib import Path
base = Path("data")
sections = ["users", "2026", "export.csv"]
path = base.joinpath(*sections)
print(path) # data/users/2026/export.csv.joinpath() is equivalent to chaining / operators. Both approaches produce the correct separator for the current operating system.
Converting between Path and string
Most standard library functions that accept paths now also accept Path objects. When you need a plain string, use str():
from pathlib import Path
p = Path("data") / "config.json"
print(str(p)) # data/config.json
print(p.as_posix()) # data/config.json.as_posix() always returns forward slashes regardless of the operating system, which is useful for URLs, logging, or any context where forward slashes are expected.
Practical example: organizing files by extension
Combine several pathlib features to create a small file organizer.
from pathlib import Path
source = Path("downloads")
organized = Path("organized")
organized.mkdir(exist_ok=True)With the source and destination folders ready, loop through every entry in the downloads folder and move each file into a subfolder named after its extension:
for file_path in source.iterdir():
if file_path.is_file():
ext = file_path.suffix.lstrip(".") or "no_ext"
dest_dir = organized / ext
dest_dir.mkdir(exist_ok=True)
file_path.rename(dest_dir / file_path.name)
print(f"Moved {file_path.name} to {ext}/")This script scans a downloads folder, creates subdirectories named after each file extension, and moves files into the corresponding folder. Files without extensions go into a no_ext folder.
Common mistakes
Treating Path objects as strings everywhere. Path objects work in most places strings work, but some older libraries expect strings. Use str(path) when a function raises a TypeError about Path vs str.
Using os.path in new code. pathlib has been the recommended way to handle paths since Python 3.4 (over a decade as of 2026). Replace os.path.join(a, b) with Path(a) / b, os.path.exists(p) with Path(p).exists(), and os.path.splitext(p) with Path(p).suffix.
Forgetting exist_ok=True with mkdir. Without this flag, calling .mkdir() on a directory that already exists raises a FileExistsError. Unless you specifically want to catch duplicate directories, always pass exist_ok=True.
Rune AI
Key Insights
Path(\"folder\") / \"file.txt\"builds cross-platform paths with the/operator..read_text()and.write_text()handle simple file I/O withoutopen()..glob(\"*.py\")finds files matching a pattern..iterdir()lists directory contents as Path objects..mkdir(parents=True)creates directories including intermediate parents..is_file(),.is_dir(), and.exists()inspect paths without opening them.pathlibworks on Windows, macOS, and Linux without path separator changes.
Frequently Asked Questions
Should I use pathlib or os.path for file paths?
How do I convert a pathlib Path to a string?
Does pathlib handle Windows and Linux paths differently?
Conclusion
pathlib is the modern way to work with file paths in Python. Use the / operator to join paths, .read_text() and .write_text() for simple file I/O, .glob() for pattern matching, and .iterdir() for directory listing. Replace os.path calls with pathlib equivalents in all new code.
More in this topic
Format Dates and Times in Python
Learn how to use strftime and strptime to format dates as strings and parse date strings into Python datetime objects.
What Is the Python Standard Library?
Learn what the Python standard library is, why it matters, and how to use its built-in modules without installing anything extra.
Encode and Decode Base64 Data in Python
Learn how to use Python's base64 module to encode binary data as text and decode Base64 strings back to bytes.