Python pathlib
Core Python Iteration and Files

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.

GoalBy the end you can build paths with /, read their name, suffix and parent, create folders, read and write small files, list files with glob and rglob, and rename or delete them safely.

Before this lesson: Python Strings, Python File Handling

2Python pathlib: diagram

Python pathlibp = Path("reports/2025/sales.final.csv")expressionresultp.name"sales.final.csv"p.stem"sales.final"p.suffix".csv"p.suffixes[".final", ".csv"]p.parentPath("reports/2025")p.parts("reports", "2025", "sales.final.csv")p.with_suffix(".txt")Path("reports/2025/sales.final.txt")with_suffix() builds a new Path; the file on disk is not renamed
A Path knows its own pieces, so you read them as attributes instead of slicing strings. Methods such as with_suffix() return a new Path and leave the real file alone.

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.

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

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

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

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

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

Python
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

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.

Interactive example
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.

  1. Line 1-2Path comes from pathlib. shutil is only used at the end to delete the demo folder again.
  2. Line 4root is a Path object for demo_files. Nothing has been created on disk yet.
  3. Line 5The / operator joins the pieces into demo_files/reports/2025/sales.csv. This is still only a description of a location, and the file does not exist.
  4. Line 6report.parent is the folder demo_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.
  5. Line 7write_text() creates the file, writes the three lines into it and closes it again, all in one call.
  6. Line 9Printing a Path shows the joined location.
  7. Line 10name is sales.csv, suffix is .csv and parent is the folder around the file.
  8. Line 11exists() is now True, and stat().st_size reports the file size in bytes: 27 characters, all single bytes.
  9. Line 12read_text() returns the whole file as one string. splitlines() turns it into a list, and [1] picks the second line, Jan,120.
  10. Line 14shutil.rmtree() deletes the whole demo_files tree. Path.rmdir() could only remove an empty folder.
  11. Line 15The report object still exists, but the file behind it is gone, so exists() is False.

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)
TypeError: unsupported operand type(s) for +: 'PosixPath' and 'str'

✓ 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())
FileNotFoundError: [Errno 44] No such file or directory: 'report.txt'

✓ 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()
FileNotFoundError: [Errno 44] No such file or directory: 'demo_files/2025/q1'

✓ 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())
No error, but it prints True False: the file on disk was not renamed

✓ 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"))
<map object at 0x7f3498258640> (the address differs on every run)

✓ 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

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

Your code
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))

2. Predict the path pieces

Predict the output Easy

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

Read this code
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:])

2 more exercises → 6 more examples

Finished this lesson?
Progress is saved in this browser — create a free account to keep it across devices.

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