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:
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
reportsThe / 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
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
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:
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
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
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
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
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.path | pathlib |
|---|---|
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 questionsWhat 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.
Virtual Environments
Give every project its own set of packages, and stop them fighting.