Skip to content

Commit 2085494

Browse files
committed
pythonGH-102783: Speed up pathlib.PurePath.__fspath__() by returning raw path
Return an unnormalized path from `pathlib.PurePath.__fspath__()`. This is equivalent to a normalized path (because pathlib's normalization doesn't change the meaning of paths), but considerably cheaper to generate.
1 parent 1619f43 commit 2085494

File tree

2 files changed

+26
-18
lines changed

2 files changed

+26
-18
lines changed

Lib/pathlib.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ class PurePath:
209209
# in the `__init__()` method.
210210
'_raw_paths',
211211

212+
# The `_raw_path_cached` slot stores a joined but unnormalized string
213+
# path. This is set in the `__init__()` method or `_raw_path`
214+
# property. It's returned from `__fspath__()`, and used as the basis
215+
# for the normalized path parts (see following slots).
216+
'_raw_path_cached',
217+
212218
# The `_drv`, `_root` and `_tail_cached` slots store parsed and
213219
# normalized parts of the path. They are set when any of the `drive`,
214220
# `root` or `_tail` properties are accessed for the first time. The
@@ -273,19 +279,6 @@ def _parse_path(cls, path):
273279
parsed = [sys.intern(str(x)) for x in rel.split(sep) if x and x != '.']
274280
return drv, root, parsed
275281

276-
def _load_parts(self):
277-
paths = self._raw_paths
278-
if len(paths) == 0:
279-
path = ''
280-
elif len(paths) == 1:
281-
path = paths[0]
282-
else:
283-
path = self.pathmod.join(*paths)
284-
drv, root, tail = self._parse_path(path)
285-
self._drv = drv
286-
self._root = root
287-
self._tail_cached = tail
288-
289282
def _from_parsed_parts(self, drv, root, tail):
290283
path_str = self._format_parsed_parts(drv, root, tail)
291284
path = self.with_segments(path_str)
@@ -321,13 +314,22 @@ def as_posix(self):
321314
def __repr__(self):
322315
return "{}({!r})".format(self.__class__.__name__, self.as_posix())
323316

317+
@property
318+
def _raw_path(self):
319+
"""The joined (but unnormalized) string path."""
320+
try:
321+
return self._raw_path_cached
322+
except AttributeError:
323+
self._raw_path_cached = self.pathmod.join(*self._raw_paths)
324+
return self._raw_path_cached
325+
324326
@property
325327
def drive(self):
326328
"""The drive prefix (letter or UNC path), if any."""
327329
try:
328330
return self._drv
329331
except AttributeError:
330-
self._load_parts()
332+
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
331333
return self._drv
332334

333335
@property
@@ -336,15 +338,15 @@ def root(self):
336338
try:
337339
return self._root
338340
except AttributeError:
339-
self._load_parts()
341+
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
340342
return self._root
341343

342344
@property
343345
def _tail(self):
344346
try:
345347
return self._tail_cached
346348
except AttributeError:
347-
self._load_parts()
349+
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
348350
return self._tail_cached
349351

350352
@property
@@ -605,6 +607,10 @@ def __init__(self, *args):
605607
f"not {type(path).__name__!r}")
606608
paths.append(path)
607609
self._raw_paths = paths
610+
if len(paths) == 1:
611+
self._raw_path_cached = paths[0]
612+
elif len(paths) == 0:
613+
self._raw_path_cached = ''
608614
self._resolving = False
609615

610616
def __reduce__(self):
@@ -613,12 +619,12 @@ def __reduce__(self):
613619
return (self.__class__, self.parts)
614620

615621
def __fspath__(self):
616-
return str(self)
622+
return self._raw_path
617623

618624
def __bytes__(self):
619625
"""Return the bytes representation of the path. This is only
620626
recommended to use under Unix."""
621-
return os.fsencode(self)
627+
return os.fsencode(str(self))
622628

623629
@property
624630
def _str_normcase(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Speed up ``pathlib.PurePath.__fspath__()`` by returning an unnormalized
2+
path.

0 commit comments

Comments
 (0)