Python pathlib
1Python pathlib: overview
pathlib's Path is an object that represents a file or folder location, with operators and methods to join, take apart, check, read, list, rename and delete paths the same way on every operating system.
Before this lesson: Python Strings, Python File Handling
2Python pathlib: diagram
3Python pathlib: explained step by step
Almost every program that touches files has to juggle paths: join a folder and a file name, find the extension, check whether something exists. You can do all of it with plain strings, but then you worry about / versus \, doubled slashes and string slicing. The pathlib module gives you a Path object that already understands how paths work.
from pathlib import Path
A Path is an object, not a string
Path("notes.txt") only describes a location. It does not open, create or check anything, so you can build paths to files that do not exist yet. The / operator joins pieces with the right separator for the operating system.
from pathlib import Path
base = Path("demo_files")
report = base / "reports" / "2025" / "sales.csv"
print(report)
print(report == Path("demo_files/reports/2025/sales.csv"))Relative paths like these start from the current working directory. This lesson never prints that directory, because it differs from machine to machine. On Windows a Path prints with backslashes; the examples here show the forward-slash form you get in the browser.
Taking a path apart
A Path knows its own pieces: name (last part), stem (name without the final suffix), suffix (the final extension), suffixes (all of them), parent (the folder around it) and parts (a tuple of every piece). with_suffix() and with_name() return a new Path with one piece swapped.
from pathlib import Path
photo = Path("photos/2025/trip.final.jpg")
print(photo.name)
print(photo.stem, photo.suffix)
print(photo.suffixes)
print(photo.parent)
print(photo.parts)
print(photo.with_suffix(".png"))
print(photo.with_name("cover.jpg"))Nothing on disk changed here. These methods only calculate another path; renaming a real file is a separate step you will meet below.
Asking questions about a path
exists() says whether the path points at anything. is_file() and is_dir() tell files and folders apart. Ask before you read or delete, because a path can be perfectly valid and still point at nothing.
Folders, text files and sizes
mkdir(parents=True, exist_ok=True) creates a folder, including any missing parent folders, and does nothing if it is already there. Without parents=True a missing parent raises FileNotFoundError; without exist_ok=True the second run raises FileExistsError. For small text files, write_text() creates or overwrites the file in one call and read_text() returns everything. stat().st_size is the size in bytes.
from pathlib import Path
import shutil
folder = Path("demo_files") / "notes"
folder.mkdir(parents=True, exist_ok=True)
note = folder / "todo.txt"
print(note.write_text("Buy milk\nCall Sam\n"))
print(note.read_text().splitlines())
print(note.stat().st_size, "bytes")
print(note.exists(), note.is_file(), folder.is_dir())
shutil.rmtree("demo_files")write_text() returns the number of characters it wrote. For huge files or line-by-line work, use with note.open() as f: instead; a Path works anywhere open() accepts a file name.
Listing files: iterdir, glob and rglob
iterdir() gives the direct children of a folder, glob("*.txt") the matches inside one folder, and rglob("*.txt") the matches in that folder and every sub-folder. They return lazy iterators, and the order is not guaranteed, so wrap them in sorted() whenever the order matters.
from pathlib import Path
import shutil
root = Path("demo_files")
(root / "archive").mkdir(parents=True, exist_ok=True)
for name in ["b.txt", "a.txt", "data.csv"]:
(root / name).write_text("x\n")
(root / "archive" / "old.txt").write_text("old\n")
print([p.name for p in sorted(root.iterdir())])
print([p.name for p in sorted(root.glob("*.txt"))])
print([p.as_posix() for p in sorted(root.rglob("*.txt"))])
shutil.rmtree(root)Renaming and deleting
rename(new_path) moves or renames and returns the new Path. unlink() deletes a file (missing_ok=True silences the error if it is already gone). rmdir() removes a folder, but only an empty one; shutil.rmtree(folder) removes a whole tree. Deleting is permanent: there is no recycle bin.
from pathlib import Path
folder = Path("demo_files")
folder.mkdir(exist_ok=True)
draft = folder / "draft.txt"
draft.write_text("first version\n")
final = draft.rename(folder / "final.txt")
print(draft.exists(), final.exists())
final.unlink()
final.unlink(missing_ok=True)
folder.rmdir()
print(folder.exists())pathlib compared with strings and os.path
The older tools still work, and pathlib can be mixed with them. But look at the difference:
import os
from pathlib import Path
folder, name = "reports", "sales.csv"
print(folder + "/" + name)
print(os.path.join(folder, name))
print(Path(folder) / name)
print(os.path.splitext(name)[1], Path(name).suffix)String gluing breaks on Windows and when a piece already ends with a slash. os.path is safe but reads inside-out as nested function calls. With pathlib you read left to right, and the file operations are methods on the same object. If a library insists on a plain string, call str(path).
Quick reference: Python pathlib
- Python File Handling Cheat Sheet: open, read, write, CSV, JSON — Open files with `with open(path, mode, encoding="utf-8") as f:` so they close automatically, read with `read(…
- How to Read a File Line by Line in Python — Open the file with `with open("file.txt") as f:` and loop over it: `for line in f:`. Each `line` is one line …
- How to Check if a File Exists in Python — Use `pathlib`: `Path("data.txt").is_file()` is `True` only if the path exists and is a regular file. `Path(..…
- Python open() Function — `open(file, mode='r', encoding=None)` opens a file and returns a file object you can read from or write to; u…
4Python pathlib: syntax
from pathlib import Path
p = Path("data") / "reports" / "sales.csv" # join with /, never with +
p.name p.stem p.suffix p.suffixes # "sales.csv" "sales" ".csv" [".csv"]
p.parent p.parts # Path("data/reports"), ("data", "reports", "sales.csv")
p.with_suffix(".txt") p.with_name("x.csv") # new Path only, nothing renamed on disk
p.exists() p.is_file() p.is_dir() # ask before you act
p.parent.mkdir(parents=True, exist_ok=True) # create folders (all missing parents)
p.write_text("text") p.read_text() # whole small file (write overwrites)
p.stat().st_size # size in bytes
sorted(folder.iterdir()) # direct children
sorted(folder.glob("*.txt")) # matches in one folder
sorted(folder.rglob("*.txt")) # matches in all sub-folders
p.relative_to(folder) p.as_posix() str(p) # shorter path / "/" text / plain string
p.rename(new_path) # move or rename, returns the new Path
p.unlink(missing_ok=True) # delete a file
folder.rmdir() # delete an EMPTY folder (shutil.rmtree for the rest)
5Python pathlib: run the example
Press Run (or Ctrl + Enter). The output (step 6) appears directly below the code. Change anything and run it again.
from pathlib import Path
import shutil
root = Path("demo_files")
report = root / "reports" / "2025" / "sales.csv"
report.parent.mkdir(parents=True, exist_ok=True)
report.write_text("month,total\nJan,120\nFeb,95\n")
print(report)
print(report.name, report.suffix, report.parent)
print(report.exists(), report.stat().st_size, "bytes")
print(report.read_text().splitlines()[1])
shutil.rmtree(root)
print(report.exists())Expected outputdemo_files/reports/2025/sales.csv
sales.csv .csv demo_files/reports/2025
True 27 bytes
Jan,120
False
7Python pathlib: how the example works
Hover or tap a step to highlight its lines in the example above.
- Line 1-2
Pathcomes frompathlib.shutilis only used at the end to delete the demo folder again. - Line 4
rootis a Path object fordemo_files. Nothing has been created on disk yet. - Line 5The
/operator joins the pieces intodemo_files/reports/2025/sales.csv. This is still only a description of a location, and the file does not exist. - Line 6
report.parentis the folderdemo_files/reports/2025.mkdir(parents=True, exist_ok=True)creates it together with every missing parent, and does not complain if you run the program again. - Line 7
write_text()creates the file, writes the three lines into it and closes it again, all in one call. - Line 9Printing a Path shows the joined location.
- Line 10
nameissales.csv,suffixis.csvandparentis the folder around the file. - Line 11
exists()is nowTrue, andstat().st_sizereports the file size in bytes: 27 characters, all single bytes. - Line 12
read_text()returns the whole file as one string.splitlines()turns it into a list, and[1]picks the second line,Jan,120. - Line 14
shutil.rmtree()deletes the wholedemo_filestree.Path.rmdir()could only remove an empty folder. - Line 15The
reportobject still exists, but the file behind it is gone, soexists()isFalse.
8Python pathlib: common mistakes
Joining a Path and a string with +
✗ Wrong
from pathlib import Path
folder = Path("reports")
report = folder + "sales.csv"
print(report)✓ Right
from pathlib import Path
folder = Path("reports")
report = folder / "sales.csv"
print(report)A Path is not a string, so + does not work. Join paths with the / operator, which also picks the right separator for the operating system.
Reading a file that does not exist
✗ Wrong
from pathlib import Path
report = Path("report.txt")
print(report.read_text())✓ Right
from pathlib import Path
report = Path("report.txt")
if report.exists():
print(report.read_text())
else:
print("No report yet")A Path can describe a file that is not there, but read_text() needs the real file. Check exists() first (or catch FileNotFoundError). Relative paths also depend on the current folder, so make sure the file is where the program looks.
Forgetting parents=True when creating nested folders
✗ Wrong
from pathlib import Path
Path("demo_files/2025/q1").mkdir()✓ Right
from pathlib import Path
folder = Path("demo_files/2025/q1")
folder.mkdir(parents=True, exist_ok=True)
print(folder.is_dir())mkdir() creates only the last folder, so the missing parents make it fail. Add parents=True, and add exist_ok=True as well so that running the program a second time does not raise FileExistsError.
Expecting with_suffix() to rename the file
✗ Wrong
from pathlib import Path
note = Path("note.txt")
note.write_text("hello")
note.with_suffix(".md")
print(note.exists(), Path("note.md").exists())✓ Right
from pathlib import Path
note = Path("note.txt")
note.write_text("hello")
new_note = note.rename(note.with_suffix(".md"))
print(note.exists(), new_note.exists())with_suffix() and with_name() only build a new Path object. To change the real file, pass that new path to rename().
Printing glob() instead of a sorted list of files
✗ Wrong
from pathlib import Path
folder = Path("photos")
folder.mkdir(exist_ok=True)
for name in ["b.jpg", "a.jpg", "c.jpg"]:
(folder / name).write_text("")
print(folder.glob("*.jpg"))✓ Right
from pathlib import Path
folder = Path("photos")
folder.mkdir(exist_ok=True)
for name in ["b.jpg", "a.jpg", "c.jpg"]:
(folder / name).write_text("")
print([p.name for p in sorted(folder.glob("*.jpg"))])glob(), rglob() and iterdir() are lazy, and the files come out in no guaranteed order. Wrap the call in sorted() and print what you need, such as the names.
9Python pathlib: exercises
Write your answer, then press Check. The checks run in your browser.
1. Build a report path
Write code Easy ✓ CompletedWrite report_path(base, year, month) that returns a Path (not a string) in this shape: <base>/reports/<year>/sales_<month>.csv, where the month always has two digits. For example report_path("exports", 2025, 3) is exports/reports/2025/sales_03.csv. Join the pieces with /. The program prints the path for the starter call.
from pathlib import Path
def report_path(base, year, month):
# build exports/reports/2025/sales_03.csv as a Path
pass
print(report_path("exports", 2025, 3))
from pathlib import Path
def report_path(base, year, month):
return Path(base) / "reports" / str(year) / f"sales_{month:02d}.csv"
print(report_path("exports", 2025, 3))
2. Predict the path pieces
Predict the output Easy ✓ CompletedRead the code and type exactly what it prints. Nothing is created on disk, so think only about the pieces of the path. Then press Check.
from pathlib import Path
p = Path("photos/2025/trip.final.jpg")
print(p.stem)
print(p.suffixes)
print(p.with_suffix(".png"))
print(p.parent.name)
print(p.parts[-2:])
trip.final
['.final', '.jpg']
photos/2025/trip.final.png
2025
('2025', 'trip.final.jpg')
2 more exercises → 6 more examples
Questions learners ask
Should I use pathlib or os.path?
Use pathlib for new code. It reads left to right, joins paths with /, and puts file operations such as read_text(), exists() and glob() on the same object. os.path still works and you will see it in older code, and the two mix well because functions that accept a file name also accept a Path. When a library wants a plain string, use str(path).
Why do I get FileNotFoundError even though the file exists?
A relative path such as Path("data.txt") is resolved from the current working directory, which is not always the folder of your script. Check Path.cwd() and path.resolve() to see where Python is really looking, and check path.exists() before reading. Also make sure the parent folders exist when you write: mkdir(parents=True, exist_ok=True) creates them.
How do I get the files in a folder in alphabetical order?
Wrap the call in sorted(): sorted(folder.glob("*.txt")). iterdir(), glob() and rglob() return a lazy iterator and do not promise any order, so the same code can list files differently on different computers. Sorting makes the result stable; use sorted(..., key=lambda p: p.stat().st_size) to sort by size instead.
Further reading
10Next lesson
Up nextPython Classes and ObjectsA class is a blueprint that describes what data a kind of thing holds and what it can do; an object is one concrete thi…Related:
Python File HandlingPython with StatementWorking with JSON in PythonPython CSV Files
← Python with Statement · All Iteration and Files lessons · Practise in the IDE