Chapters
Python114 chapters

Files and errorsChapter 87 of 114

Paths with pathlib

Build, inspect and search file paths without worrying about slashes.

A path is an object

pathlib replaces string juggling with something that knows it is a path:

Python
from pathlib import Path

p = Path("data") / "reports" / "march.csv"

print(p.parts)
print(p.name, p.stem, p.suffix)
print(p.parent.name)

Output

('data', 'reports', 'march.csv')
march.csv march .csv
reports

The / operator joins with whatever separator the platform uses, so the same code is right on Windows and everywhere else. Printing the path itself would show backslashes on Windows and forward slashes here, which is exactly the detail you no longer have to think about.

Reading and writing small files

Python
from pathlib import Path

p = Path("notes.txt")
p.write_text("one line\n", encoding="utf-8")

print(p.read_text(encoding="utf-8").strip())
print(p.exists(), p.is_file(), p.is_dir())
print(len(p.read_text(encoding="utf-8")))

Output

one line
True True False
9

One call each, opened and closed for you. For anything you build up incrementally, keep using with open(...).

Making folders

Python
from pathlib import Path

Path("data/reports").mkdir(parents=True, exist_ok=True)
Path("data/reports/march.csv").write_text("name\n", encoding="utf-8")

print(Path("data/reports").is_dir())
print(sorted(p.name for p in Path("data/reports").iterdir()))

Output

True
['march.csv']

parents=True creates the whole chain, and exist_ok=True means "make sure it exists" rather than failing when it already does.

Finding files

glob matches within one folder; rglob searches all the way down:

Python
from pathlib import Path

Path("tree/inner").mkdir(parents=True, exist_ok=True)
for name in ["tree/a.txt", "tree/b.csv", "tree/inner/c.txt"]:
    Path(name).write_text("x", encoding="utf-8")

print(sorted(p.name for p in Path("tree").glob("*.txt")))
print(sorted(p.name for p in Path("tree").rglob("*.txt")))
print(sorted(p.name for p in Path("tree").rglob("*")))

Output

['a.txt']
['a.txt', 'c.txt']
['a.txt', 'b.csv', 'c.txt', 'inner']

glob found one file; rglob reached into the subfolder. Both return a generator, so a huge tree costs nothing until you iterate it.

Deleting

Python
from pathlib import Path

Path("scratch.txt").write_text("x", encoding="utf-8")
Path("scratch.txt").unlink()
print(Path("scratch.txt").exists())

Path("gone.txt").unlink(missing_ok=True)
print("no error for a missing file")

Output

False
no error for a missing file

missing_ok=True says exactly what you mean, with none of the race you get from checking exists() first.

Changing part of a path

Python
from pathlib import Path

p = Path("data/march.csv")

print(p.with_suffix(".json").name)
print(p.with_name("april.csv").name)
print(p.with_stem("april").name)

Output

march.json
april.csv
april.csv

These return new paths. A Path never changes in place.

Absolute, relative and resolved

Python
from pathlib import Path

p = Path("data/march.csv")

print(p.is_absolute())
print(p.resolve().is_absolute())
print(Path("/a/b/c.txt").relative_to("/a").as_posix())

Output

False
True
b/c.txt

resolve() makes a path absolute and removes any .. segments, which is the check to run before using a path that came from outside your program.

Where your script lives

Python
from pathlib import Path

HERE = Path(__file__).resolve().parent
config = HERE / "config.json"

Building paths relative to __file__ rather than the working directory means your script works no matter which folder it is run from. It is the single most useful line in this chapter.

os.path, translated

os.pathpathlib
os.path.join(a, b)Path(a) / b
os.path.basename(p)p.name
os.path.dirname(p)p.parent
os.path.splitext(p)[1]p.suffix
os.path.exists(p)p.exists()
os.makedirs(p, exist_ok=True)p.mkdir(parents=True, exist_ok=True)
os.remove(p)p.unlink()
os.listdir(p)p.iterdir()

Test yourself

2 questions

What does Path("data") / "march.csv" do?

Show the answer

Joins them with the right separator for the platform — That is why the same code works on Windows and elsewhere without any string juggling.

Why build paths from Path(__file__).parent rather than a relative path?

Show the answer

The script then works whatever folder it is run from — A relative path is resolved against the working directory, which is wherever the user happened to be.

Next chapter

Virtual Environments

Give every project its own set of packages, and stop them fighting.