Skip to content

gh-117607: Speedup os.path.relpath() #117608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 1, 2024
21 changes: 12 additions & 9 deletions Lib/ntpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,33 +785,36 @@ def realpath(path, *, strict=False):
def relpath(path, start=None):
"""Return a relative version of a path"""
path = os.fspath(path)
if not path:
raise ValueError("no path specified")

if isinstance(path, bytes):
sep = b'\\'
curdir = b'.'
pardir = b'..'
getcwd = os.getcwdb
else:
sep = '\\'
curdir = '.'
pardir = '..'
getcwd = os.getcwd

if start is None:
start = curdir
else:
start = os.fspath(start)

if not path:
raise ValueError("no path specified")

start = os.fspath(start)
try:
start_abs = abspath(normpath(start))
path_abs = abspath(normpath(path))
start_abs = getcwd() if not start or start == curdir else abspath(start)
path_abs = abspath(path)
start_drive, _, start_rest = splitroot(start_abs)
path_drive, _, path_rest = splitroot(path_abs)
if normcase(start_drive) != normcase(path_drive):
raise ValueError("path is on mount %r, start on mount %r" % (
path_drive, start_drive))

start_list = [x for x in start_rest.split(sep) if x]
path_list = [x for x in path_rest.split(sep) if x]
start_list = start_rest.split(sep) if start_rest else []
path_list = path_rest.split(sep) if path_rest else []
# Work out how much of the filepath is shared by start and path.
i = 0
for e1, e2 in zip(start_list, path_list):
Expand All @@ -822,7 +825,7 @@ def relpath(path, start=None):
rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
if not rel_list:
return curdir
return join(*rel_list)
return sep.join(rel_list)
except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning):
genericpath._check_arg_types('relpath', path, start)
raise
Expand Down
12 changes: 9 additions & 3 deletions Lib/posixpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,26 +510,32 @@ def relpath(path, start=None):
curdir = b'.'
sep = b'/'
pardir = b'..'
getcwd = os.getcwdb
else:
curdir = '.'
sep = '/'
pardir = '..'
getcwd = os.getcwd

if start is None:
start = curdir
else:
start = os.fspath(start)

try:
start_list = [x for x in abspath(start).split(sep) if x]
path_list = [x for x in abspath(path).split(sep) if x]
start_abs = getcwd() if not start or start == curdir else abspath(start)
path_abs = abspath(path)
start_tail = start_abs.lstrip(sep)
path_tail = path_abs.lstrip(sep)
start_list = start_tail.split(sep) if start_tail else []
path_list = path_tail.split(sep) if path_tail else []
# Work out how much of the filepath is shared by start and path.
i = len(commonprefix([start_list, path_list]))

rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
if not rel_list:
return curdir
return join(*rel_list)
return sep.join(rel_list)
except (TypeError, AttributeError, BytesWarning, DeprecationWarning):
genericpath._check_arg_types('relpath', path, start)
raise
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Speedup :func:`os.path.relpath()` by up to 1.9 times on Windows, up to ?.? times on Unix.