Skip to content

GH-113528: Deoptimise pathlib._abc.PurePathBase.relative_to() (again) #113882

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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions Lib/pathlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import posixpath
import sys
import warnings
from itertools import chain
from _collections_abc import Sequence

try:
Expand Down Expand Up @@ -245,10 +246,19 @@ def relative_to(self, other, /, *_deprecated, walk_up=False):
"scheduled for removal in Python 3.14")
warnings.warn(msg, DeprecationWarning, stacklevel=2)
other = self.with_segments(other, *_deprecated)
path = _abc.PurePathBase.relative_to(self, other, walk_up=walk_up)
path._drv = path._root = ''
path._tail_cached = path._raw_paths.copy()
return path
elif not isinstance(other, PurePath):
other = self.with_segments(other)
for step, path in enumerate(chain([other], other.parents)):
if path == self or path in self.parents:
break
elif not walk_up:
raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}")
elif path.name == '..':
raise ValueError(f"'..' segment in {str(other)!r} cannot be walked")
else:
raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors")
parts = ['..'] * step + self._tail[len(path._tail):]
return self._from_parsed_parts('', '', parts)

def is_relative_to(self, other, /, *_deprecated):
"""Return True if the path is relative to another path or False.
Expand All @@ -259,7 +269,9 @@ def is_relative_to(self, other, /, *_deprecated):
"scheduled for removal in Python 3.14")
warnings.warn(msg, DeprecationWarning, stacklevel=2)
other = self.with_segments(other, *_deprecated)
return _abc.PurePathBase.is_relative_to(self, other)
elif not isinstance(other, PurePath):
other = self.with_segments(other)
return other == self or other in self.parents

def as_uri(self):
"""Return the path as a URI."""
Expand Down
35 changes: 25 additions & 10 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import posixpath
import sys
from errno import ENOENT, ENOTDIR, EBADF, ELOOP, EINVAL
from itertools import chain
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO

#
Expand Down Expand Up @@ -358,24 +357,40 @@ def relative_to(self, other, *, walk_up=False):
"""
if not isinstance(other, PurePathBase):
other = self.with_segments(other)
for step, path in enumerate(chain([other], other.parents)):
if path == self or path in self.parents:
break
anchor0, parts0 = self._stack
anchor1, parts1 = other._stack
if anchor0 != anchor1:
raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors")
while parts0 and parts1 and parts0[-1] == parts1[-1]:
parts0.pop()
parts1.pop()
for part in parts1:
if not part or part == '.':
pass
elif not walk_up:
raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}")
elif path.name == '..':
elif part == '..':
raise ValueError(f"'..' segment in {str(other)!r} cannot be walked")
else:
raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors")
parts = ['..'] * step + self._tail[len(path._tail):]
return self.with_segments(*parts)
else:
parts0.append('..')
return self.with_segments('', *reversed(parts0))

def is_relative_to(self, other):
"""Return True if the path is relative to another path or False.
"""
if not isinstance(other, PurePathBase):
other = self.with_segments(other)
return other == self or other in self.parents
anchor0, parts0 = self._stack
anchor1, parts1 = other._stack
if anchor0 != anchor1:
return False
while parts0 and parts1 and parts0[-1] == parts1[-1]:
parts0.pop()
parts1.pop()
for part in parts1:
if part and part != '.':
return False
return True

@property
def parts(self):
Expand Down