Work with File Paths using Python `pathlib`

Learn how to use Python's pathlib module to build, inspect, and manipulate file paths in a clean, cross-platform way.

7 min read

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.

pythonpython
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)  # .py

Path.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.

pythonpython
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/project

Path("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.

pythonpython
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')
AttributeDescriptionExample
.nameFinal path componentreport.pdf
.stemFilename without extensionreport
.suffixFile extension with dot.pdf
.parentDirectory containing the file/Users/maya/Documents
.partsTuple of all path components('/', 'Users', 'maya', 'Documents', 'report.pdf')

These attributes let you derive related paths without string manipulation:

pythonpython
from pathlib import Path
 
file = Path("data/input.csv")
output = file.parent / (file.stem + "_processed" + file.suffix)
 
print(output)  # data/input_processed.csv

Checking 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.

pythonpython
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().

pythonpython
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():

pythonpython
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.

pythonpython
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.

pythonpython
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:

texttext
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.

pythonpython
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:

texttext
main.py
main.py
src/utils.py
src/models.py
tests/test_utils.py

glob(".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:

PatternMatches
*.txtAll .txt files in the current directory
**/*.jsonAll .json files recursively
data/**/backupAll directories named backup under data/
report_2026*.csvCSV files starting with report_2026

Building portable paths

Never hardcode \ or / in path strings. Use pathlib and the / operator instead.

pythonpython
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():

pythonpython
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.

pythonpython
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:

pythonpython
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

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 without open().
  • .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.
  • pathlib works on Windows, macOS, and Linux without path separator changes.
RunePowered by Rune AI

Frequently Asked Questions

Should I use pathlib or os.path for file paths?

Use `pathlib` for all new Python code. It is more readable, object-oriented, and works across platforms without manual string manipulation. The `os.path` module still works but `pathlib` is the modern, recommended approach since Python 3.4.

How do I convert a pathlib Path to a string?

Pass the Path object to `str()`: `str(Path('/home/user'))` returns `'/home/user'`. Most Python functions that accept paths now also accept Path objects directly, so explicit conversion is rarely needed.

Does pathlib handle Windows and Linux paths differently?

Yes. On Windows, `Path` uses backslashes and is case-insensitive where the filesystem allows. On Linux and macOS, it uses forward slashes and is case-sensitive. You can write one codebase and `pathlib` handles the platform differences automatically.

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.