Skip to content

Commit 1092cfb

Browse files
authored
GH-113528: Deoptimise pathlib._abc.PathBase.resolve() (#113782)
Replace use of `_from_parsed_parts()` with `with_segments()` in `resolve()`. No effect on `Path.resolve()`, which uses `os.path.realpath()`.
1 parent 3a9096c commit 1092cfb

File tree

1 file changed

+40
-25
lines changed

1 file changed

+40
-25
lines changed

Lib/pathlib/_abc.py

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,23 @@ def __rtruediv__(self, key):
406406
except TypeError:
407407
return NotImplemented
408408

409+
@property
410+
def _stack(self):
411+
"""
412+
Split the path into a 2-tuple (anchor, parts), where *anchor* is the
413+
uppermost parent of the path (equivalent to path.parents[-1]), and
414+
*parts* is a reversed list of parts following the anchor.
415+
"""
416+
split = self.pathmod.split
417+
path = str(self)
418+
parent, name = split(path)
419+
names = []
420+
while path != parent:
421+
names.append(name)
422+
path = parent
423+
parent, name = split(path)
424+
return path, names
425+
409426
@property
410427
def parent(self):
411428
"""The logical parent of the path."""
@@ -911,60 +928,60 @@ def readlink(self):
911928
self._unsupported("readlink")
912929
readlink._supported = False
913930

914-
def _split_stack(self):
915-
"""
916-
Split the path into a 2-tuple (anchor, parts), where *anchor* is the
917-
uppermost parent of the path (equivalent to path.parents[-1]), and
918-
*parts* is a reversed list of parts following the anchor.
919-
"""
920-
if not self._tail:
921-
return self, []
922-
return self._from_parsed_parts(self.drive, self.root, []), self._tail[::-1]
923-
924931
def resolve(self, strict=False):
925932
"""
926933
Make the path absolute, resolving all symlinks on the way and also
927934
normalizing it.
928935
"""
929936
if self._resolving:
930937
return self
931-
path, parts = self._split_stack()
938+
path_root, parts = self._stack
939+
path = self.with_segments(path_root)
932940
try:
933941
path = path.absolute()
934942
except UnsupportedOperation:
935-
pass
943+
path_tail = []
944+
else:
945+
path_root, path_tail = path._stack
946+
path_tail.reverse()
936947

937948
# If the user has *not* overridden the `readlink()` method, then symlinks are unsupported
938949
# and (in non-strict mode) we can improve performance by not calling `stat()`.
939950
querying = strict or getattr(self.readlink, '_supported', True)
940951
link_count = 0
941952
while parts:
942953
part = parts.pop()
954+
if not part or part == '.':
955+
continue
943956
if part == '..':
944-
if not path._tail:
945-
if path.root:
957+
if not path_tail:
958+
if path_root:
946959
# Delete '..' segment immediately following root
947960
continue
948-
elif path._tail[-1] != '..':
961+
elif path_tail[-1] != '..':
949962
# Delete '..' segment and its predecessor
950-
path = path.parent
963+
path_tail.pop()
951964
continue
952-
next_path = path._make_child_relpath(part)
965+
path_tail.append(part)
953966
if querying and part != '..':
954-
next_path._resolving = True
967+
path = self.with_segments(path_root + self.pathmod.sep.join(path_tail))
968+
path._resolving = True
955969
try:
956-
st = next_path.stat(follow_symlinks=False)
970+
st = path.stat(follow_symlinks=False)
957971
if S_ISLNK(st.st_mode):
958972
# Like Linux and macOS, raise OSError(errno.ELOOP) if too many symlinks are
959973
# encountered during resolution.
960974
link_count += 1
961975
if link_count >= self._max_symlinks:
962976
raise OSError(ELOOP, "Too many symbolic links in path", str(self))
963-
target, target_parts = next_path.readlink()._split_stack()
977+
target_root, target_parts = path.readlink()._stack
964978
# If the symlink target is absolute (like '/etc/hosts'), set the current
965979
# path to its uppermost parent (like '/').
966-
if target.root:
967-
path = target
980+
if target_root:
981+
path_root = target_root
982+
path_tail.clear()
983+
else:
984+
path_tail.pop()
968985
# Add the symlink target's reversed tail parts (like ['hosts', 'etc']) to
969986
# the stack of unresolved path parts.
970987
parts.extend(target_parts)
@@ -976,9 +993,7 @@ def resolve(self, strict=False):
976993
raise
977994
else:
978995
querying = False
979-
next_path._resolving = False
980-
path = next_path
981-
return path
996+
return self.with_segments(path_root + self.pathmod.sep.join(path_tail))
982997

983998
def symlink_to(self, target, target_is_directory=False):
984999
"""

0 commit comments

Comments
 (0)