diff options
author | Barney Gale <barney.gale@gmail.com> | 2024-01-13 08:03:21 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-13 08:03:21 (GMT) |
commit | f20b151a1c49602282a0e80ffb92f68c28c04abd (patch) | |
tree | a6297cc95d132dda30a1c961cd608a2ffdbb81f4 /Lib/pathlib | |
parent | e4ff131e01184b68d868cfd241a03f8b7d2e0ff9 (diff) | |
download | cpython-f20b151a1c49602282a0e80ffb92f68c28c04abd.zip cpython-f20b151a1c49602282a0e80ffb92f68c28c04abd.tar.gz cpython-f20b151a1c49602282a0e80ffb92f68c28c04abd.tar.bz2 |
pathlib ABCs: add `_raw_path` property (#113976)
It's wrong for the `PurePathBase` methods to rely so much on `__str__()`.
Instead, they should treat the raw path(s) as opaque objects and leave the
details to `pathmod`.
This commit adds a `PurePathBase._raw_path` property and uses it through
many of the other ABC methods. These methods are all redefined in
`PurePath` and `Path`, so this has no effect on the public classes.
Diffstat (limited to 'Lib/pathlib')
-rw-r--r-- | Lib/pathlib/__init__.py | 12 | ||||
-rw-r--r-- | Lib/pathlib/_abc.py | 39 |
2 files changed, 31 insertions, 20 deletions
diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index 9d3fcd8..e70cfe9 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -257,7 +257,9 @@ class PurePath(_abc.PurePathBase): parsed = [sys.intern(str(x)) for x in rel.split(sep) if x and x != '.'] return drv, root, parsed - def _load_parts(self): + @property + def _raw_path(self): + """The joined but unnormalized path.""" paths = self._raw_paths if len(paths) == 0: path = '' @@ -265,7 +267,7 @@ class PurePath(_abc.PurePathBase): path = paths[0] else: path = self.pathmod.join(*paths) - self._drv, self._root, self._tail_cached = self._parse_path(path) + return path @property def drive(self): @@ -273,7 +275,7 @@ class PurePath(_abc.PurePathBase): 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 @@ -282,7 +284,7 @@ class PurePath(_abc.PurePathBase): 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 @@ -290,7 +292,7 @@ class PurePath(_abc.PurePathBase): 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 diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index d2a31ed..e53921e 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -163,10 +163,15 @@ class PurePathBase: """ return type(self)(*pathsegments) + @property + def _raw_path(self): + """The joined but unnormalized path.""" + return self.pathmod.join(*self._raw_paths) + def __str__(self): """Return the string representation of the path, suitable for passing to system calls.""" - return self.pathmod.join(*self._raw_paths) + return self._raw_path def as_posix(self): """Return the string representation of the path with forward (/) @@ -176,23 +181,23 @@ class PurePathBase: @property def drive(self): """The drive prefix (letter or UNC path), if any.""" - return self.pathmod.splitdrive(str(self))[0] + return self.pathmod.splitdrive(self._raw_path)[0] @property def root(self): """The root of the path, if any.""" - return self.pathmod.splitroot(str(self))[1] + return self.pathmod.splitroot(self._raw_path)[1] @property def anchor(self): """The concatenation of the drive and root, or ''.""" - drive, root, _ = self.pathmod.splitroot(str(self)) + drive, root, _ = self.pathmod.splitroot(self._raw_path) return drive + root @property def name(self): """The final path component, if any.""" - return self.pathmod.basename(str(self)) + return self.pathmod.basename(self._raw_path) @property def suffix(self): @@ -236,7 +241,7 @@ class PurePathBase: dirname = self.pathmod.dirname if dirname(name): raise ValueError(f"Invalid name {name!r}") - return self.with_segments(dirname(str(self)), name) + return self.with_segments(dirname(self._raw_path), name) def with_stem(self, stem): """Return a new path with the stem changed.""" @@ -266,8 +271,10 @@ class PurePathBase: other = self.with_segments(other) anchor0, parts0 = self._stack anchor1, parts1 = other._stack + if isinstance(anchor0, str) != isinstance(anchor1, str): + raise TypeError(f"{self._raw_path!r} and {other._raw_path!r} have different types") if anchor0 != anchor1: - raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors") + raise ValueError(f"{self._raw_path!r} and {other._raw_path!r} have different anchors") while parts0 and parts1 and parts0[-1] == parts1[-1]: parts0.pop() parts1.pop() @@ -275,9 +282,9 @@ class PurePathBase: if not part or part == '.': pass elif not walk_up: - raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}") + raise ValueError(f"{self._raw_path!r} is not in the subpath of {other._raw_path!r}") elif part == '..': - raise ValueError(f"'..' segment in {str(other)!r} cannot be walked") + raise ValueError(f"'..' segment in {other._raw_path!r} cannot be walked") else: parts0.append('..') return self.with_segments('', *reversed(parts0)) @@ -289,6 +296,8 @@ class PurePathBase: other = self.with_segments(other) anchor0, parts0 = self._stack anchor1, parts1 = other._stack + if isinstance(anchor0, str) != isinstance(anchor1, str): + raise TypeError(f"{self._raw_path!r} and {other._raw_path!r} have different types") if anchor0 != anchor1: return False while parts0 and parts1 and parts0[-1] == parts1[-1]: @@ -336,7 +345,7 @@ class PurePathBase: *parts* is a reversed list of parts following the anchor. """ split = self.pathmod.split - path = str(self) + path = self._raw_path parent, name = split(path) names = [] while path != parent: @@ -348,7 +357,7 @@ class PurePathBase: @property def parent(self): """The logical parent of the path.""" - path = str(self) + path = self._raw_path parent = self.pathmod.dirname(path) if path != parent: parent = self.with_segments(parent) @@ -360,7 +369,7 @@ class PurePathBase: def parents(self): """A sequence of this path's logical parents.""" dirname = self.pathmod.dirname - path = str(self) + path = self._raw_path parent = dirname(path) parents = [] while path != parent: @@ -379,7 +388,7 @@ class PurePathBase: return True return False else: - return self.pathmod.isabs(str(self)) + return self.pathmod.isabs(self._raw_path) def is_reserved(self): """Return True if the path contains one of the special names reserved @@ -894,7 +903,7 @@ class PathBase(PurePathBase): # encountered during resolution. link_count += 1 if link_count >= self._max_symlinks: - raise OSError(ELOOP, "Too many symbolic links in path", str(self)) + raise OSError(ELOOP, "Too many symbolic links in path", self._raw_path) target_root, target_parts = path.readlink()._stack # If the symlink target is absolute (like '/etc/hosts'), set the current # path to its uppermost parent (like '/'). @@ -908,7 +917,7 @@ class PathBase(PurePathBase): parts.extend(target_parts) continue elif parts and not S_ISDIR(st.st_mode): - raise NotADirectoryError(ENOTDIR, "Not a directory", str(self)) + raise NotADirectoryError(ENOTDIR, "Not a directory", self._raw_path) except OSError: if strict: raise |