From 37bd893a22b784d573b71df5417d855dc32dee62 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sat, 6 Jan 2024 21:17:51 +0000 Subject: GH-113528: Deoptimise `pathlib._abc.PurePathBase.parent` (#113530) Replace use of `_from_parsed_parts()` with `with_segments()`, and move assignments to `_drv`, `_root`, _tail_cached` and `_str` slots into `PurePath`. --- Lib/pathlib/__init__.py | 48 ++++++++++++++++++++++++++++++++++++++++- Lib/pathlib/_abc.py | 57 ++++++++++++++----------------------------------- 2 files changed, 63 insertions(+), 42 deletions(-) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index 765a142..d83f292 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -11,6 +11,7 @@ import os import posixpath import sys import warnings +from _collections_abc import Sequence try: import pwd @@ -31,6 +32,35 @@ __all__ = [ ] +class _PathParents(Sequence): + """This object provides sequence-like access to the logical ancestors + of a path. Don't try to construct it yourself.""" + __slots__ = ('_path', '_drv', '_root', '_tail') + + def __init__(self, path): + self._path = path + self._drv = path.drive + self._root = path.root + self._tail = path._tail + + def __len__(self): + return len(self._tail) + + def __getitem__(self, idx): + if isinstance(idx, slice): + return tuple(self[i] for i in range(*idx.indices(len(self)))) + + if idx >= len(self) or idx < -len(self): + raise IndexError(idx) + if idx < 0: + idx += len(self) + return self._path._from_parsed_parts(self._drv, self._root, + self._tail[:-idx - 1]) + + def __repr__(self): + return "<{}.parents>".format(type(self._path).__name__) + + UnsupportedOperation = _abc.UnsupportedOperation @@ -95,7 +125,6 @@ class PurePath(_abc.PurePathBase): paths.append(path) # Avoid calling super().__init__, as an optimisation self._raw_paths = paths - self._resolving = False def __reduce__(self): # Using the parts tuple helps share interned path parts @@ -167,6 +196,23 @@ class PurePath(_abc.PurePathBase): return self._parts_normcase >= other._parts_normcase @property + def parent(self): + """The logical parent of the path.""" + drv = self.drive + root = self.root + tail = self._tail + if not tail: + return self + return self._from_parsed_parts(drv, root, tail[:-1]) + + @property + def parents(self): + """A sequence of this path's logical parents.""" + # The value of this property should not be cached on the path object, + # as doing so would introduce a reference cycle. + return _PathParents(self) + + @property def name(self): """The final path component, if any.""" tail = self._tail diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 1ce3713..aca2bd5 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -2,7 +2,6 @@ import functools import ntpath import posixpath import sys -from _collections_abc import Sequence 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 @@ -138,35 +137,6 @@ class UnsupportedOperation(NotImplementedError): pass -class _PathParents(Sequence): - """This object provides sequence-like access to the logical ancestors - of a path. Don't try to construct it yourself.""" - __slots__ = ('_path', '_drv', '_root', '_tail') - - def __init__(self, path): - self._path = path - self._drv = path.drive - self._root = path.root - self._tail = path._tail - - def __len__(self): - return len(self._tail) - - def __getitem__(self, idx): - if isinstance(idx, slice): - return tuple(self[i] for i in range(*idx.indices(len(self)))) - - if idx >= len(self) or idx < -len(self): - raise IndexError(idx) - if idx < 0: - idx += len(self) - return self._path._from_parsed_parts(self._drv, self._root, - self._tail[:-idx - 1]) - - def __repr__(self): - return "<{}.parents>".format(type(self._path).__name__) - - class PurePathBase: """Base class for pure path objects. @@ -442,21 +412,26 @@ class PurePathBase: @property def parent(self): """The logical parent of the path.""" - drv = self.drive - root = self.root - tail = self._tail - if not tail: - return self - path = self._from_parsed_parts(drv, root, tail[:-1]) - path._resolving = self._resolving - return path + path = str(self) + parent = self.pathmod.dirname(path) + if path != parent: + parent = self.with_segments(parent) + parent._resolving = self._resolving + return parent + return self @property def parents(self): """A sequence of this path's logical parents.""" - # The value of this property should not be cached on the path object, - # as doing so would introduce a reference cycle. - return _PathParents(self) + dirname = self.pathmod.dirname + path = str(self) + parent = dirname(path) + parents = [] + while path != parent: + parents.append(self.with_segments(parent)) + path = parent + parent = dirname(path) + return tuple(parents) def is_absolute(self): """True if the path is absolute (has both a root and, if applicable, -- cgit v0.12