PurePath.parts

parts splits a path into its components (the anchor counts as one). parent drops the last component, parents lists every ancestor up to the anchor, and anchor = drive + root tells you whether the path is rooted.

pathlib attributePython 3.4+Live demo
Common call
p.parent, p.parts
Returns
PurePosixPath('/usr/local'), ('/', 'usr', 'local', 'bin')
Replaces
os.path.dirname and path.split("/")
Watch out
parent of '.' is '.', parent of '/' is '/': it never raises
PurePath.parts / .parent / .parents / .anchor / .drive / .root
→ tuple[str, ...] | PurePath | Sequence[PurePath] | str

Demo

Live evaluation
The anchor (here the root "/") is its own first part.
Try:
Inputs
pathstra path
Code
from pathlib import PurePosixPath
PurePosixPath('/usr/local/bin').parts
Result
('/', 'usr', 'local', 'bin')

parents stops at the anchor: for an absolute path the last parent is '/', for a relative one it is '.'. A single-part relative path has '.' as its parent, and the root is its own parent — parent never raises, so a loop 'while p != p.parent' always ends. POSIX paths never have a drive; see PureWindowsPath for 'C:' and UNC shares.

Common patterns

Walk up to find a project root
Check the path and each ancestor for a marker file.
from pathlib import Path
def find_root(start, marker='pyproject.toml'):
    for folder in (start, *start.parents):
        if (folder / marker).exists():
            return folder
    return None
Grandparent folder
parents[1] is two levels up (parents[0] is parent).
from pathlib import Path
repo = Path(__file__).resolve().parents[1]
First folder of a relative path
parts[0] is the top-level directory.
from pathlib import PurePosixPath
top = PurePosixPath('src/pkg/mod.py').parts[0]  # 'src'

Examples

1. Components as a tuple
from pathlib import PurePosixPath PurePosixPath('/usr/bin/python3').parts
Returns
('/', 'usr', 'bin', 'python3')
2. The containing folder
from pathlib import PurePosixPath PurePosixPath('/usr/bin/python3').parent
Returns
PurePosixPath('/usr/bin')
3. Index into parents
from pathlib import PurePosixPath p = PurePosixPath('/a/b/c/d') (p.parents[0], p.parents[2])
Returns
(PurePosixPath('/a/b/c'), PurePosixPath('/a'))
4. Negative indexes and slices (3.10+)
from pathlib import PurePosixPath p = PurePosixPath('/a/b/c') (p.parents[-1], p.parents[:2])
Returns
(PurePosixPath('/'), (PurePosixPath('/a/b'), PurePosixPath('/a')))
5. Parent of a bare name
from pathlib import PurePosixPath PurePosixPath('notes.txt').parent
Returns
PurePosixPath('.')
6. Anchor tells rooted from relative
from pathlib import PurePosixPath (PurePosixPath('/etc').anchor, PurePosixPath('etc').anchor)
Returns
('/', '')
7. Windows drive and root
from pathlib import PureWindowsPath p = PureWindowsPath('D:/games/save.dat') (p.drive, p.root, p.parts)
Returns
('D:', '\\', ('D:\\', 'games', 'save.dat'))
8. Out of range
from pathlib import PurePosixPath PurePosixPath('a/b').parents[2]
Returns
IndexError: 2

Pitfalls

1. Expecting parent to resolve ".."
parent is lexical: it removes the last component, even when that component is '..'.
parent of '..'
from pathlib import PurePosixPath
PurePosixPath('../x').parent.parent
PurePosixPath('.')
resolve() first
from pathlib import Path
Path('x').mkdir()
Path('x/..').resolve() == Path.cwd().resolve()
True
2. Printing parents directly
parents is a lazy sequence object; its repr is not the list of paths. Convert it with list() or tuple().
repr(parents)
from pathlib import PurePosixPath
PurePosixPath('/a/b').parents
<PurePosixPath.parents>
list(parents)
from pathlib import PurePosixPath
list(PurePosixPath('/a/b').parents)
[PurePosixPath('/a'), PurePosixPath('/')]
3. Splitting str(path) on "/"
String splitting keeps empty pieces and breaks on Windows separators; parts is already split and normalised.
str.split('/')
'/usr//local/'.split('/')
['', 'usr', '', 'local', '']
.parts
from pathlib import PurePosixPath
PurePosixPath('/usr//local/').parts
('/', 'usr', 'local')

When to use

Use it
  • Folder of a file (parent) and walking upwards (parents)
  • Inspecting the top-level folder of a relative path (parts[0])
  • Checking whether a path is rooted (anchor)
Reach for something else
  • Resolving ".." or symlinks → Path.resolve()
  • Relative path between two locations → relative_to()

Notes

CPython impl
Lib/pathlib/_local.py: parts = (drive + root,) + tail when anchored; parents is a _PathParents sequence built from the parsed tail
drive
Always '' for POSIX; 'C:' or '\\server\share' for Windows
root
'/' or '' on POSIX ('//' for exactly two leading slashes); '\' or '' on Windows
Changed in 3.10
parents supports slices and negative indexes

FAQ

Path(p).parent. For two levels up use p.parent.parent or p.parents[1]. Call resolve() first if the path may contain '..' or be relative.