mirror of https://github.com/python/cpython.git
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.
This commit is contained in:
parent
e4ff131e01
commit
f20b151a1c
|
@ -257,7 +257,9 @@ def _parse_path(cls, path):
|
||||||
parsed = [sys.intern(str(x)) for x in rel.split(sep) if x and x != '.']
|
parsed = [sys.intern(str(x)) for x in rel.split(sep) if x and x != '.']
|
||||||
return drv, root, parsed
|
return drv, root, parsed
|
||||||
|
|
||||||
def _load_parts(self):
|
@property
|
||||||
|
def _raw_path(self):
|
||||||
|
"""The joined but unnormalized path."""
|
||||||
paths = self._raw_paths
|
paths = self._raw_paths
|
||||||
if len(paths) == 0:
|
if len(paths) == 0:
|
||||||
path = ''
|
path = ''
|
||||||
|
@ -265,7 +267,7 @@ def _load_parts(self):
|
||||||
path = paths[0]
|
path = paths[0]
|
||||||
else:
|
else:
|
||||||
path = self.pathmod.join(*paths)
|
path = self.pathmod.join(*paths)
|
||||||
self._drv, self._root, self._tail_cached = self._parse_path(path)
|
return path
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def drive(self):
|
def drive(self):
|
||||||
|
@ -273,7 +275,7 @@ def drive(self):
|
||||||
try:
|
try:
|
||||||
return self._drv
|
return self._drv
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self._load_parts()
|
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
|
||||||
return self._drv
|
return self._drv
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -282,7 +284,7 @@ def root(self):
|
||||||
try:
|
try:
|
||||||
return self._root
|
return self._root
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self._load_parts()
|
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
|
||||||
return self._root
|
return self._root
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -290,7 +292,7 @@ def _tail(self):
|
||||||
try:
|
try:
|
||||||
return self._tail_cached
|
return self._tail_cached
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self._load_parts()
|
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
|
||||||
return self._tail_cached
|
return self._tail_cached
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -163,10 +163,15 @@ def with_segments(self, *pathsegments):
|
||||||
"""
|
"""
|
||||||
return type(self)(*pathsegments)
|
return type(self)(*pathsegments)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _raw_path(self):
|
||||||
|
"""The joined but unnormalized path."""
|
||||||
|
return self.pathmod.join(*self._raw_paths)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Return the string representation of the path, suitable for
|
"""Return the string representation of the path, suitable for
|
||||||
passing to system calls."""
|
passing to system calls."""
|
||||||
return self.pathmod.join(*self._raw_paths)
|
return self._raw_path
|
||||||
|
|
||||||
def as_posix(self):
|
def as_posix(self):
|
||||||
"""Return the string representation of the path with forward (/)
|
"""Return the string representation of the path with forward (/)
|
||||||
|
@ -176,23 +181,23 @@ def as_posix(self):
|
||||||
@property
|
@property
|
||||||
def drive(self):
|
def drive(self):
|
||||||
"""The drive prefix (letter or UNC path), if any."""
|
"""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
|
@property
|
||||||
def root(self):
|
def root(self):
|
||||||
"""The root of the path, if any."""
|
"""The root of the path, if any."""
|
||||||
return self.pathmod.splitroot(str(self))[1]
|
return self.pathmod.splitroot(self._raw_path)[1]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def anchor(self):
|
def anchor(self):
|
||||||
"""The concatenation of the drive and root, or ''."""
|
"""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
|
return drive + root
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""The final path component, if any."""
|
"""The final path component, if any."""
|
||||||
return self.pathmod.basename(str(self))
|
return self.pathmod.basename(self._raw_path)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def suffix(self):
|
def suffix(self):
|
||||||
|
@ -236,7 +241,7 @@ def with_name(self, name):
|
||||||
dirname = self.pathmod.dirname
|
dirname = self.pathmod.dirname
|
||||||
if dirname(name):
|
if dirname(name):
|
||||||
raise ValueError(f"Invalid name {name!r}")
|
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):
|
def with_stem(self, stem):
|
||||||
"""Return a new path with the stem changed."""
|
"""Return a new path with the stem changed."""
|
||||||
|
@ -266,8 +271,10 @@ def relative_to(self, other, *, walk_up=False):
|
||||||
other = self.with_segments(other)
|
other = self.with_segments(other)
|
||||||
anchor0, parts0 = self._stack
|
anchor0, parts0 = self._stack
|
||||||
anchor1, parts1 = other._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:
|
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]:
|
while parts0 and parts1 and parts0[-1] == parts1[-1]:
|
||||||
parts0.pop()
|
parts0.pop()
|
||||||
parts1.pop()
|
parts1.pop()
|
||||||
|
@ -275,9 +282,9 @@ def relative_to(self, other, *, walk_up=False):
|
||||||
if not part or part == '.':
|
if not part or part == '.':
|
||||||
pass
|
pass
|
||||||
elif not walk_up:
|
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 == '..':
|
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:
|
else:
|
||||||
parts0.append('..')
|
parts0.append('..')
|
||||||
return self.with_segments('', *reversed(parts0))
|
return self.with_segments('', *reversed(parts0))
|
||||||
|
@ -289,6 +296,8 @@ def is_relative_to(self, other):
|
||||||
other = self.with_segments(other)
|
other = self.with_segments(other)
|
||||||
anchor0, parts0 = self._stack
|
anchor0, parts0 = self._stack
|
||||||
anchor1, parts1 = other._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:
|
if anchor0 != anchor1:
|
||||||
return False
|
return False
|
||||||
while parts0 and parts1 and parts0[-1] == parts1[-1]:
|
while parts0 and parts1 and parts0[-1] == parts1[-1]:
|
||||||
|
@ -336,7 +345,7 @@ def _stack(self):
|
||||||
*parts* is a reversed list of parts following the anchor.
|
*parts* is a reversed list of parts following the anchor.
|
||||||
"""
|
"""
|
||||||
split = self.pathmod.split
|
split = self.pathmod.split
|
||||||
path = str(self)
|
path = self._raw_path
|
||||||
parent, name = split(path)
|
parent, name = split(path)
|
||||||
names = []
|
names = []
|
||||||
while path != parent:
|
while path != parent:
|
||||||
|
@ -348,7 +357,7 @@ def _stack(self):
|
||||||
@property
|
@property
|
||||||
def parent(self):
|
def parent(self):
|
||||||
"""The logical parent of the path."""
|
"""The logical parent of the path."""
|
||||||
path = str(self)
|
path = self._raw_path
|
||||||
parent = self.pathmod.dirname(path)
|
parent = self.pathmod.dirname(path)
|
||||||
if path != parent:
|
if path != parent:
|
||||||
parent = self.with_segments(parent)
|
parent = self.with_segments(parent)
|
||||||
|
@ -360,7 +369,7 @@ def parent(self):
|
||||||
def parents(self):
|
def parents(self):
|
||||||
"""A sequence of this path's logical parents."""
|
"""A sequence of this path's logical parents."""
|
||||||
dirname = self.pathmod.dirname
|
dirname = self.pathmod.dirname
|
||||||
path = str(self)
|
path = self._raw_path
|
||||||
parent = dirname(path)
|
parent = dirname(path)
|
||||||
parents = []
|
parents = []
|
||||||
while path != parent:
|
while path != parent:
|
||||||
|
@ -379,7 +388,7 @@ def is_absolute(self):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return self.pathmod.isabs(str(self))
|
return self.pathmod.isabs(self._raw_path)
|
||||||
|
|
||||||
def is_reserved(self):
|
def is_reserved(self):
|
||||||
"""Return True if the path contains one of the special names reserved
|
"""Return True if the path contains one of the special names reserved
|
||||||
|
@ -894,7 +903,7 @@ def resolve(self, strict=False):
|
||||||
# encountered during resolution.
|
# encountered during resolution.
|
||||||
link_count += 1
|
link_count += 1
|
||||||
if link_count >= self._max_symlinks:
|
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
|
target_root, target_parts = path.readlink()._stack
|
||||||
# If the symlink target is absolute (like '/etc/hosts'), set the current
|
# If the symlink target is absolute (like '/etc/hosts'), set the current
|
||||||
# path to its uppermost parent (like '/').
|
# path to its uppermost parent (like '/').
|
||||||
|
@ -908,7 +917,7 @@ def resolve(self, strict=False):
|
||||||
parts.extend(target_parts)
|
parts.extend(target_parts)
|
||||||
continue
|
continue
|
||||||
elif parts and not S_ISDIR(st.st_mode):
|
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:
|
except OSError:
|
||||||
if strict:
|
if strict:
|
||||||
raise
|
raise
|
||||||
|
|
Loading…
Reference in New Issue