Skip to main content

Lib/genericpath.py (Shared OS-independent path utilities)

genericpath.py is the common substrate that all of CPython's os.path backends (posixpath, ntpath, macpath) delegate to for logic that does not depend on path separator or drive letter conventions. It is imported directly by each backend rather than being part of the public os.path surface.

Map

FunctionLines (approx.)Notes
exists~10os.stat wrapped in try/except
lexists~8os.lstat variant
isfile~10stat + S_ISREG
isdir~10stat + S_ISDIR
islink~8lstat + S_ISLNK
isabs~5delegated to platform backend
commonprefix~12character-by-character, NOT component
commonpath~40component-aware, raises ValueError on mixed abs/rel
_check_methods~12MRO-based protocol helper used by ABC checks
_splitext~25dot-extension splitting shared by both backends

Reading

commonprefix: character-by-character, not path-component-aware

This is the most surprising function in the module. It operates on raw characters, not on path components, which means it can return a string that is not itself a valid path prefix.

# CPython: Lib/genericpath.py ~74
def commonprefix(m):
"Given a list of pathnames, returns the longest common leading component"
m = tuple(map(os.fspath, m))
if not m: return ''
if len(m) == 1:
return m[0]
s1 = min(m)
s2 = max(m)
for i, c in enumerate(s1):
if c != s2[i]:
return s1[:i]
return s1

min/max exploit lexicographic ordering so that only two strings need to be compared character by character. If you pass ['/usr/lib', '/usr/local/lib'] you get '/usr/l', not '/usr/'. Use commonpath for component-level work.

commonpath: the component-aware alternative

Introduced in Python 3.5, commonpath splits each path into components, finds the shared prefix of those component lists, then rejoins. It raises ValueError for mixed absolute and relative inputs or for paths that cross drive boundaries on Windows.

# CPython: Lib/genericpath.py ~93
def commonpath(paths):
paths = tuple(map(os.fspath, paths))
if not paths:
raise ValueError('commonpath() arg is an empty sequence')
...
split_paths = [path.split(sep) for path in paths]
...
common = split_paths[0]
for s in split_paths[1:]:
common = common[:len(s)]
for i, (a, b) in enumerate(zip(common, s)):
if a != b:
common = common[:i]
break
prefix = sep.join(common)
...

The key invariant: every element in the returned path is an actual directory component that all inputs share.

_splitext: shared dot-extension logic

Both posixpath.splitext and ntpath.splitext call genericpath._splitext after stripping a drive or UNC prefix. The rule is that a leading dot does not count as an extension (.bashrc has no extension), but a trailing dot does (foo. splits to ('foo', '.')).

# CPython: Lib/genericpath.py ~22
def _splitext(p, sep, altsep, extsep):
sepIndex = p.rfind(sep)
if altsep:
altsepIndex = p.rfind(altsep)
sepIndex = max(sepIndex, altsepIndex)

dotIndex = p.rfind(extsep)
if dotIndex > sepIndex:
# skip all leading dots
filenameIndex = sepIndex + 1
while filenameIndex < dotIndex:
if p[filenameIndex:filenameIndex+1] != extsep:
return p[:dotIndex], p[dotIndex:]
filenameIndex += 1
return p, p[:0]

gopy mirror

Not yet ported. When ported, the natural home is module/genericpath/ or as unexported helpers inside module/os/path/. Because Go's path/filepath already provides filepath.Ext and filepath.Dir, the porting work is mostly about matching CPython's exact edge-case behaviour (leading dots, empty inputs, mixed separators) rather than reimplementing the algorithms from scratch.

Relevant tasks: none opened yet.

CPython 3.14 changes

No significant changes to genericpath.py between 3.12 and 3.14. The file is intentionally stable; new path features land in the platform-specific modules or in pathlib instead.