Skip to content

Files and Folders with pathlib

Updated 9 min read Aimerscrew

So far you've opened files by name in the current folder. Real programs need more: build paths to files in other folders, check whether a file exists before opening it, create folders, and find every photo in a directory. The Python pathlib module handles all of that with one easy-to-read tool, the Path object, and it works the same on Windows, macOS and Linux.

🌍 Real-life example

Your Downloads folder is probably a mess of PDFs, photos and music files. Sorting them by hand takes ages. With pathlib, a short script can look at every file, check its extension, create a folder like pdf or jpg, and move each file into the right place. We'll build exactly that at the end of this lesson.

What is pathlib in Python?

pathlib is part of Python's standard library, so there's nothing to install. Its main class is Path, which represents a location on your disk: a file or a folder. A Path isn't the file itself; it's more like an address. It can point to a place that doesn't exist yet.

from pathlib import Path

report = Path("reports") / "2026" / "march-sales.csv"

print(report.as_posix())
print(report.name)
print(report.stem)
print(report.suffix)
print(report.parent.as_posix())
reports/2026/march-sales.csv
march-sales.csv
march-sales
.csv
reports/2026

A few things to notice:

  • The / operator joins path parts. Python inserts the right separator for your operating system: \ on Windows, / on macOS and Linux.
  • as_posix() returns the path as a string with forward slashes on every system. We use it in this lesson so the output looks the same for everyone. A plain print(report) shows backslashes on Windows.
  • name, stem, suffix and parent are attributes: values attached to the object, so they have no parentheses.
Animation: the path reports/2026/march-sales.csv with its parts labeled: parent is reports/2026, name is march-sales.csv, stem is march-sales and suffix is .csv
Every Path can be taken apart into its parent folder, name, stem and suffix.

Path attributes at a glance

AttributeMeaningFor reports/2026/march-sales.csv
nameFinal part, with extensionmarch-sales.csv
stemName without the extensionmarch-sales
suffixThe extension, with the dot.csv
parentThe folder that contains itreports/2026
partsAll pieces as a tuple('reports', '2026', 'march-sales.csv')

You can also swap the extension with with_suffix(), which is handy when converting files:

from pathlib import Path

photo = Path("trip") / "beach.png"
print(photo.with_suffix(".jpg").as_posix())
print(photo.parts)
trip/beach.jpg
('trip', 'beach.png')

Where am I? Path.cwd() and Path.home()

Path.cwd() returns the current working directory (the folder relative paths start from), and Path.home() returns your user folder. The output depends on your computer, so yours will differ:

from pathlib import Path

print(Path.cwd())
print(Path.home())

On a Mac this might print /Users/sam/projects and /Users/sam; on Windows, something like C:\Users\sam\projects and C:\Users\sam. Paths like Path("reports") that don't start from the top of the drive are relative paths: they're measured from the current working directory.

How do you read and write a file with pathlib?

For small files, write_text() and read_text() open, write or read, and close the file in a single call:

from pathlib import Path

note = Path("reminder.txt")
note.write_text("Pick up the dry cleaning\n", encoding="utf-8")

print(note.read_text(encoding="utf-8"))
Pick up the dry cleaning

Like mode "w", write_text() replaces whatever was in the file. For bigger files or line-by-line work, a Path also works anywhere a file name does, so with open(note, encoding="utf-8") as file: is perfectly fine (see the with statement).

How do you check if a file exists in Python?

Use exists(). To know what exists at that path, use is_file() or is_dir():

from pathlib import Path

note = Path("reminder.txt")
note.write_text("Call the dentist\n", encoding="utf-8")

print(note.exists(), note.is_file(), note.is_dir())
print(Path("ghost.txt").exists())
print(Path(".").is_dir())
True True False
False
True

Path(".") means "the current folder". Checking first lets you show a friendly message instead of crashing with a FileNotFoundError:

from pathlib import Path

playlist = Path("road-trip.m3u")

if playlist.exists():
    print(playlist.read_text(encoding="utf-8"))
else:
    print(f"{playlist.name} not found. Create a playlist first!")
road-trip.m3u not found. Create a playlist first!

How do you create folders with mkdir()?

mkdir() creates a folder. Two options make it much friendlier:

  • parents=True also creates any missing parent folders.
  • exist_ok=True means "it's fine if the folder already exists".
from pathlib import Path

backup = Path("backups") / "2026" / "march"
backup.mkdir(parents=True, exist_ok=True)

print(backup.is_dir())
print(Path("backups").is_dir())
True
True

Without parents=True, this fails with FileNotFoundError if backups doesn't exist yet. Without exist_ok=True, running the script a second time fails with FileExistsError. Using both makes your script safe to run again and again.

How do you list files in a folder?

iterdir() gives you every item inside a folder, and glob() gives you only the items whose names match a pattern, where * means "any characters". The order they come back in depends on your system, so wrap them in sorted() when order matters:

from pathlib import Path

album = Path("album")
album.mkdir(exist_ok=True)
for name in ["beach.jpg", "packing-list.txt", "sunset.jpg", "map.png"]:
    (album / name).write_text("demo", encoding="utf-8")

print("Everything:")
for item in sorted(album.iterdir()):
    print(" ", item.name)

print("Only photos:")
for photo in sorted(album.glob("*.jpg")):
    print(" ", photo.name)
Everything:
  beach.jpg
  map.png
  packing-list.txt
  sunset.jpg
Only photos:
  beach.jpg
  sunset.jpg

The first loop just creates four small sample files so you have something to list. To search subfolders too, use rglob("*.jpg"), which looks through every folder inside album as well.

Real-world example: organizing downloads by extension

Here's the Downloads cleaner. The first part creates some sample files so you can try it safely. The second part moves each file into a folder named after its extension, using replace(), which moves a file to a new path:

from pathlib import Path

downloads = Path("downloads")
downloads.mkdir(exist_ok=True)
for name in ["invoice.pdf", "holiday.JPG", "resume.pdf", "song.mp3", "selfie.jpg", "README"]:
    (downloads / name).write_text("demo", encoding="utf-8")

for item in sorted(downloads.iterdir()):
    if item.is_file() and item.suffix:
        folder = downloads / item.suffix[1:].lower()
        folder.mkdir(exist_ok=True)
        item.replace(folder / item.name)
        print(f"Moved {item.name} -> {folder.name}/")

print()
for folder in sorted(downloads.iterdir()):
    if folder.is_dir():
        names = sorted([file.name for file in folder.iterdir()])
        print(f"{folder.name}: {names}")
Moved holiday.JPG -> jpg/
Moved invoice.pdf -> pdf/
Moved resume.pdf -> pdf/
Moved selfie.jpg -> jpg/
Moved song.mp3 -> mp3/

jpg: ['holiday.JPG', 'selfie.jpg']
mp3: ['song.mp3']
pdf: ['invoice.pdf', 'resume.pdf']

Some details that make it robust:

  • item.suffix[1:] slices off the dot (".pdf" becomes "pdf"), and lower() puts .JPG and .jpg in the same folder.
  • if item.is_file() and item.suffix skips folders and files without an extension, like README, which stays where it is.
  • sorted() processes the files in alphabetical order, so the output is the same every time you run it.

⚠️ Be careful

replace() overwrites a file that already exists at the target path, and there's no undo. Test file-moving scripts on a copy of your folder before running them on real files.

pathlib vs os.path

Older tutorials use the os and os.path modules for the same jobs. They still work, but pathlib code is usually shorter and easier to read:

Taskos / os.pathpathlib
Join pathsos.path.join("a", "b.txt")Path("a") / "b.txt"
Check existenceos.path.exists(p)p.exists()
File nameos.path.basename(p)p.name
Create foldersos.makedirs(p, exist_ok=True)p.mkdir(parents=True, exist_ok=True)
List a folderos.listdir(p)p.iterdir()
Read a whole fileopen(p).read()p.read_text()

Common mistakes with pathlib

1. Adding strings and paths with +

"reports/" + Path("march.csv") raises a TypeError, because + only joins strings. Use / to join paths, or str(path) if you really need text.

2. Putting a slash at the start of a later part

A part that starts with / is treated as a fresh path from the top of the drive, and everything before it is dropped:

from pathlib import Path

print((Path("data") / "/reports").as_posix())
print((Path("data") / "reports").as_posix())
/reports
data/reports

3. Forgetting parents=True or exist_ok=True

A plain mkdir() fails if a parent folder is missing or the folder already exists. Pass both options unless you specifically want those errors.

4. Expecting iterdir() or glob() in alphabetical order

The order depends on the operating system. Wrap the result in sorted() whenever order matters, for example in reports or tests.

Practice exercise

🏋️ Try it yourself

Create the folder projects/website (including projects) in one call. Inside it, write three files: index.html, style.css and todo.txt. Then print the name of every file that is not a .txt file, in alphabetical order, followed by how many files the folder holds.

Show solution
from pathlib import Path

site = Path("projects") / "website"
site.mkdir(parents=True, exist_ok=True)

(site / "index.html").write_text("<h1>Hello</h1>", encoding="utf-8")
(site / "style.css").write_text("h1 { color: navy; }", encoding="utf-8")
(site / "todo.txt").write_text("Add a contact page", encoding="utf-8")

files = sorted(site.iterdir())
for file in files:
    if file.suffix != ".txt":
        print(file.name)

print(len(files), "files in", site.as_posix())
index.html
style.css
3 files in projects/website

What's next?

Congratulations, you've finished the File Handling chapter! You can now read, write, organize and exchange data with files. Next, start the Object-Oriented Programming chapter and learn to create your own types with Python classes and objects.

Key takeaways

  • pathlib is a built-in module whose Path objects represent file and folder locations.
  • Join paths with the / operator; Python uses the right separator for your operating system.
  • name, stem, suffix and parent give you the parts of a path; with_suffix() swaps the extension.
  • exists(), is_file() and is_dir() check what is at a path before you use it.
  • mkdir(parents=True, exist_ok=True) creates folders safely, even if they already exist.
  • iterdir() lists a folder and glob("*.jpg") filters by pattern; wrap them in sorted() for a predictable order.
  • read_text() and write_text() read or write a small file in a single call.

Frequently asked questions

What is pathlib used for in Python?

pathlib works with file system paths as objects. It lets you join paths, get file names and extensions, check whether files exist, create folders, list folder contents and read or write files, all with one Path class.

How do I check if a file exists in Python?

Create a Path and call exists(): Path("data.csv").exists() returns True or False. Use is_file() or is_dir() if you also need to know whether it is a file or a folder.

How do I create a folder if it does not exist in Python?

Call Path("folder/subfolder").mkdir(parents=True, exist_ok=True). parents=True creates any missing parent folders and exist_ok=True prevents an error if the folder is already there.

How do I list all files in a folder with pathlib?

Loop over Path("folder").iterdir() for every item, or use Path("folder").glob("*.txt") to match a pattern. Use rglob() to include subfolders, and sorted() to get a predictable order.

Should I use pathlib or os.path?

For new code, pathlib is usually the better choice because it is shorter, easier to read and object-oriented. os.path still works and appears in a lot of older code, so it is worth recognizing.