diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 9bce5320ef68e9..0bf75f25c7064e 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -209,6 +209,12 @@ class PurePath: # in the `__init__()` method. '_raw_paths', + # The `_raw_path_cached` slot stores a joined but unnormalized string + # path. This is set in the `__init__()` method or `_raw_path` + # property. It's returned from `__fspath__()`, and used as the basis + # for the normalized path parts (see following slots). + '_raw_path_cached', + # The `_drv`, `_root` and `_tail_cached` slots store parsed and # normalized parts of the path. They are set when any of the `drive`, # `root` or `_tail` properties are accessed for the first time. The @@ -255,8 +261,6 @@ def with_segments(self, *pathsegments): @classmethod def _parse_path(cls, path): - if not path: - return '', '', [] sep = cls.pathmod.sep altsep = cls.pathmod.altsep if altsep: @@ -273,19 +277,6 @@ def _parse_path(cls, path): parsed = [sys.intern(str(x)) for x in rel.split(sep) if x and x != '.'] return drv, root, parsed - def _load_parts(self): - paths = self._raw_paths - if len(paths) == 0: - path = '' - elif len(paths) == 1: - path = paths[0] - else: - path = self.pathmod.join(*paths) - drv, root, tail = self._parse_path(path) - self._drv = drv - self._root = root - self._tail_cached = tail - def _from_parsed_parts(self, drv, root, tail): path_str = self._format_parsed_parts(drv, root, tail) path = self.with_segments(path_str) @@ -321,13 +312,22 @@ def as_posix(self): def __repr__(self): return "{}({!r})".format(self.__class__.__name__, self.as_posix()) + @property + def _raw_path(self): + """The joined (but unnormalized) string path.""" + try: + return self._raw_path_cached + except AttributeError: + self._raw_path_cached = self.pathmod.join(*self._raw_paths) + return self._raw_path_cached + @property def drive(self): """The drive prefix (letter or UNC path), if any.""" try: return self._drv except AttributeError: - self._load_parts() + self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path) return self._drv @property @@ -336,7 +336,7 @@ def root(self): try: return self._root except AttributeError: - self._load_parts() + self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path) return self._root @property @@ -344,7 +344,7 @@ def _tail(self): try: return self._tail_cached except AttributeError: - self._load_parts() + self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path) return self._tail_cached @property @@ -603,8 +603,15 @@ def __init__(self, *args): "argument should be a str or an os.PathLike " "object where __fspath__ returns a str, " f"not {type(path).__name__!r}") - paths.append(path) + if path: + paths.append(path) self._raw_paths = paths + if len(paths) == 1: + self._raw_path_cached = paths[0] + elif len(paths) == 0: + self._str = self._raw_path_cached = '.' + self._drv = self._root = '' + self._tail_cached = [] self._resolving = False def __reduce__(self): @@ -613,12 +620,12 @@ def __reduce__(self): return (self.__class__, self.parts) def __fspath__(self): - return str(self) + return self._raw_path def __bytes__(self): """Return the bytes representation of the path. This is only recommended to use under Unix.""" - return os.fsencode(self) + return os.fsencode(str(self)) @property def _str_normcase(self): diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index e1121a9d76c040..b1cac781b6802e 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -708,6 +708,13 @@ def test_fspath_common(self): p = P('a/b') self._check_str(p.__fspath__(), ('a/b',)) self._check_str(os.fspath(p), ('a/b',)) + self.assertEqual('.', P().__fspath__()) + self.assertEqual('.', P('').__fspath__()) + self.assertEqual('.', P('.').__fspath__()) + self.assertEqual('.', P('', '').__fspath__()) + self.assertEqual('.', P('.', '').__fspath__()) + self.assertEqual('.', P('', '.').__fspath__()) + self.assertEqual(f'.{self.sep}.', P('.', '.').__fspath__()) def test_bytes(self): P = self.cls diff --git a/Misc/NEWS.d/next/Library/2023-11-25-12-46-21.gh-issue-102783.O-toHf.rst b/Misc/NEWS.d/next/Library/2023-11-25-12-46-21.gh-issue-102783.O-toHf.rst new file mode 100644 index 00000000000000..2e7526df9d8acc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-25-12-46-21.gh-issue-102783.O-toHf.rst @@ -0,0 +1,2 @@ +Speed up ``pathlib.PurePath.__fspath__()`` by returning an unnormalized +path.