Absolute, resolved and relative paths

pathlib · Practical · 9 min

Comparing paths, printing friendly names and checking containment all need normalised paths.

Three ways to make a path absolute:

  • p.absolute() prefixes the cwd; it doesn't touch .. or symlinks.
  • p.resolve() makes it absolute and removes .. and symlinks, giving the canonical location. Use it before comparing paths or checking containment. With strict=True it raises if the path doesn't exist.
  • p.expanduser() replaces a leading ~ with the home directory. Paths never expand ~ automatically.

p.relative_to(base) returns the part of p below base: Path("/srv/app/static/a.css").relative_to("/srv/app") gives static/a.css. It raises ValueError if p isn't under base. Since 3.12, walk_up=True allows results with .. segments.

p.is_relative_to(base) returns a bool instead of raising, but it compares strings of parts, not real locations: Path("/srv/app/../etc") "is relative to" /srv/app. Resolve both sides first when the path comes from users.

p.samefile(other) checks whether two paths point to the same file on disk, accounting for links.

Pure string normalisation like os.path.normpath has no direct pathlib equivalent, because collapsing a/link/.. without asking the file system can be wrong when link is a symlink.

Example

from pathlib import Path

Path("site/static").mkdir(parents=True)
Path("site/static/a.css").touch()
Path("site/latest").symlink_to("static")

p = Path("site/static/../static/a.css")
print(p.absolute() == Path.cwd() / p)          # True: no normalisation
print(p.resolve() == Path.cwd() / "site/static/a.css")   # True
print(Path("site/latest/a.css").resolve().parent.name)   # static

print(Path("/srv/app/static/a.css").relative_to("/srv/app"))  # static/a.css
print(Path("/srv/app").relative_to("/srv/app/static", walk_up=True))  # ..
print(Path("/srv/app/../etc").is_relative_to("/srv/app"))   # True (!)
print(Path("~/notes").expanduser().is_absolute())           # True

Remember

  • resolve() canonicalises: absolute, no .., symlinks followed
  • relative_to strips a base prefix and raises if the path isn't under it
  • is_relative_to is lexical; resolve user-supplied paths first

Watch out for

  • Treating Path('~/x') as the home folder without calling expanduser().

Check yourself

  1. Which call gives a canonical path suitable for checking whether two paths refer to the same location?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/pathlib.html#pathlib.Path.resolve