DocsHub
File Handling

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

Much 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/projects

The / 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.txt

This 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 directory

A 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
venv

Filter 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.py

Renaming 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())   # True

file — 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.json

This 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

Taskos.pathpathlib
Join pathsos.path.join(a, b)Path(a) / b
Get filenameos.path.basename(p)p.name
Get directoryos.path.dirname(p)p.parent
Get extensionos.path.splitext(p)[1]p.suffix
Check existsos.path.exists(p)p.exists()
Is fileos.path.isfile(p)p.is_file()
Is directoryos.path.isdir(p)p.is_dir()
Make directoryos.makedirs(p)p.mkdir(parents=True)
Absolute pathos.path.abspath(p)p.resolve()

Summary

ConceptExample
Create a pathPath("folder/file.txt")
Build with /Path("home") / "ali" / "file.txt"
Filenamep.name
Name without extensionp.stem
Extensionp.suffix
Parent directoryp.parent
Check existsp.exists()
Read textp.read_text(encoding="utf-8")
Write textp.write_text("content", encoding="utf-8")
Create directoryp.mkdir(parents=True, exist_ok=True)
List directoryp.iterdir()
Find by patternp.glob("*.py")
Find recursivelyp.rglob("*.py")
Delete filep.unlink(missing_ok=True)
Script-relative pathPath(__file__).parent / "data"

On this page