Skip to content

Commit 37bd893

Browse files
authored
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`.
1 parent 1e914ad commit 37bd893

File tree

2 files changed

+63
-42
lines changed

2 files changed

+63
-42
lines changed

Lib/pathlib/__init__.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import posixpath
1212
import sys
1313
import warnings
14+
from _collections_abc import Sequence
1415

1516
try:
1617
import pwd
@@ -31,6 +32,35 @@
3132
]
3233

3334

35+
class _PathParents(Sequence):
36+
"""This object provides sequence-like access to the logical ancestors
37+
of a path. Don't try to construct it yourself."""
38+
__slots__ = ('_path', '_drv', '_root', '_tail')
39+
40+
def __init__(self, path):
41+
self._path = path
42+
self._drv = path.drive
43+
self._root = path.root
44+
self._tail = path._tail
45+
46+
def __len__(self):
47+
return len(self._tail)
48+
49+
def __getitem__(self, idx):
50+
if isinstance(idx, slice):
51+
return tuple(self[i] for i in range(*idx.indices(len(self))))
52+
53+
if idx >= len(self) or idx < -len(self):
54+
raise IndexError(idx)
55+
if idx < 0:
56+
idx += len(self)
57+
return self._path._from_parsed_parts(self._drv, self._root,
58+
self._tail[:-idx - 1])
59+
60+
def __repr__(self):
61+
return "<{}.parents>".format(type(self._path).__name__)
62+
63+
3464
UnsupportedOperation = _abc.UnsupportedOperation
3565

3666

@@ -95,7 +125,6 @@ def __init__(self, *args):
95125
paths.append(path)
96126
# Avoid calling super().__init__, as an optimisation
97127
self._raw_paths = paths
98-
self._resolving = False
99128

100129
def __reduce__(self):
101130
# Using the parts tuple helps share interned path parts
@@ -166,6 +195,23 @@ def __ge__(self, other):
166195
return NotImplemented
167196
return self._parts_normcase >= other._parts_normcase
168197

198+
@property
199+
def parent(self):
200+
"""The logical parent of the path."""
201+
drv = self.drive
202+
root = self.root
203+
tail = self._tail
204+
if not tail:
205+
return self
206+
return self._from_parsed_parts(drv, root, tail[:-1])
207+
208+
@property
209+
def parents(self):
210+
"""A sequence of this path's logical parents."""
211+
# The value of this property should not be cached on the path object,
212+
# as doing so would introduce a reference cycle.
213+
return _PathParents(self)
214+
169215
@property
170216
def name(self):
171217
"""The final path component, if any."""

Lib/pathlib/_abc.py

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import ntpath
33
import posixpath
44
import sys
5-
from _collections_abc import Sequence
65
from errno import ENOENT, ENOTDIR, EBADF, ELOOP, EINVAL
76
from itertools import chain
87
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
@@ -138,35 +137,6 @@ class UnsupportedOperation(NotImplementedError):
138137
pass
139138

140139

141-
class _PathParents(Sequence):
142-
"""This object provides sequence-like access to the logical ancestors
143-
of a path. Don't try to construct it yourself."""
144-
__slots__ = ('_path', '_drv', '_root', '_tail')
145-
146-
def __init__(self, path):
147-
self._path = path
148-
self._drv = path.drive
149-
self._root = path.root
150-
self._tail = path._tail
151-
152-
def __len__(self):
153-
return len(self._tail)
154-
155-
def __getitem__(self, idx):
156-
if isinstance(idx, slice):
157-
return tuple(self[i] for i in range(*idx.indices(len(self))))
158-
159-
if idx >= len(self) or idx < -len(self):
160-
raise IndexError(idx)
161-
if idx < 0:
162-
idx += len(self)
163-
return self._path._from_parsed_parts(self._drv, self._root,
164-
self._tail[:-idx - 1])
165-
166-
def __repr__(self):
167-
return "<{}.parents>".format(type(self._path).__name__)
168-
169-
170140
class PurePathBase:
171141
"""Base class for pure path objects.
172142
@@ -442,21 +412,26 @@ def __rtruediv__(self, key):
442412
@property
443413
def parent(self):
444414
"""The logical parent of the path."""
445-
drv = self.drive
446-
root = self.root
447-
tail = self._tail
448-
if not tail:
449-
return self
450-
path = self._from_parsed_parts(drv, root, tail[:-1])
451-
path._resolving = self._resolving
452-
return path
415+
path = str(self)
416+
parent = self.pathmod.dirname(path)
417+
if path != parent:
418+
parent = self.with_segments(parent)
419+
parent._resolving = self._resolving
420+
return parent
421+
return self
453422

454423
@property
455424
def parents(self):
456425
"""A sequence of this path's logical parents."""
457-
# The value of this property should not be cached on the path object,
458-
# as doing so would introduce a reference cycle.
459-
return _PathParents(self)
426+
dirname = self.pathmod.dirname
427+
path = str(self)
428+
parent = dirname(path)
429+
parents = []
430+
while path != parent:
431+
parents.append(self.with_segments(parent))
432+
path = parent
433+
parent = dirname(path)
434+
return tuple(parents)
460435

461436
def is_absolute(self):
462437
"""True if the path is absolute (has both a root and, if applicable,

0 commit comments

Comments
 (0)