mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
GH-128520: More consistent type-checking behaviour in pathlib (#130199)
In the following methods, skip casting of the argument to a path object if the argument has a `with_segments` attribute. In `PurePath`: `relative_to()`, `is_relative_to()`, `match()`, and `full_match()`. In `Path`: `rename()`, `replace()`, `copy()`, `copy_into()`, `move()`, and `move_into()`. Previously the check varied a bit from method to method. The `PurePath` methods used `isinstance(arg, PurePath)`; the `rename()` and `replace()` methods always cast, and the remaining `Path` methods checked for a private `_copy_writer` attribute. We apply identical changes to relevant methods of the private ABCs. This improves performance a bit, because `isinstance()` checks on ABCs are expensive.
This commit is contained in:
parent
286c517db0
commit
d88677ac20
3 changed files with 24 additions and 18 deletions
|
@ -192,7 +192,7 @@ class JoinablePath(ABC):
|
||||||
Return True if this path matches the given glob-style pattern. The
|
Return True if this path matches the given glob-style pattern. The
|
||||||
pattern is matched against the entire path.
|
pattern is matched against the entire path.
|
||||||
"""
|
"""
|
||||||
if not isinstance(pattern, JoinablePath):
|
if not hasattr(pattern, 'with_segments'):
|
||||||
pattern = self.with_segments(pattern)
|
pattern = self.with_segments(pattern)
|
||||||
if case_sensitive is None:
|
if case_sensitive is None:
|
||||||
case_sensitive = self.parser.normcase('Aa') == 'Aa'
|
case_sensitive = self.parser.normcase('Aa') == 'Aa'
|
||||||
|
@ -286,7 +286,7 @@ class ReadablePath(JoinablePath):
|
||||||
"""Iterate over this subtree and yield all existing files (of any
|
"""Iterate over this subtree and yield all existing files (of any
|
||||||
kind, including directories) matching the given relative pattern.
|
kind, including directories) matching the given relative pattern.
|
||||||
"""
|
"""
|
||||||
if not isinstance(pattern, JoinablePath):
|
if not hasattr(pattern, 'with_segments'):
|
||||||
pattern = self.with_segments(pattern)
|
pattern = self.with_segments(pattern)
|
||||||
anchor, parts = _explode_path(pattern)
|
anchor, parts = _explode_path(pattern)
|
||||||
if anchor:
|
if anchor:
|
||||||
|
@ -348,7 +348,7 @@ class ReadablePath(JoinablePath):
|
||||||
"""
|
"""
|
||||||
Recursively copy this file or directory tree to the given destination.
|
Recursively copy this file or directory tree to the given destination.
|
||||||
"""
|
"""
|
||||||
if not hasattr(target, '_copy_writer'):
|
if not hasattr(target, 'with_segments'):
|
||||||
target = self.with_segments(target)
|
target = self.with_segments(target)
|
||||||
|
|
||||||
# Delegate to the target path's CopyWriter object.
|
# Delegate to the target path's CopyWriter object.
|
||||||
|
@ -366,7 +366,7 @@ class ReadablePath(JoinablePath):
|
||||||
name = self.name
|
name = self.name
|
||||||
if not name:
|
if not name:
|
||||||
raise ValueError(f"{self!r} has an empty name")
|
raise ValueError(f"{self!r} has an empty name")
|
||||||
elif hasattr(target_dir, '_copy_writer'):
|
elif hasattr(target_dir, 'with_segments'):
|
||||||
target = target_dir / name
|
target = target_dir / name
|
||||||
else:
|
else:
|
||||||
target = self.with_segments(target_dir, name)
|
target = self.with_segments(target_dir, name)
|
||||||
|
|
|
@ -475,7 +475,7 @@ class PurePath:
|
||||||
The *walk_up* parameter controls whether `..` may be used to resolve
|
The *walk_up* parameter controls whether `..` may be used to resolve
|
||||||
the path.
|
the path.
|
||||||
"""
|
"""
|
||||||
if not isinstance(other, PurePath):
|
if not hasattr(other, 'with_segments'):
|
||||||
other = self.with_segments(other)
|
other = self.with_segments(other)
|
||||||
for step, path in enumerate(chain([other], other.parents)):
|
for step, path in enumerate(chain([other], other.parents)):
|
||||||
if path == self or path in self.parents:
|
if path == self or path in self.parents:
|
||||||
|
@ -492,7 +492,7 @@ class PurePath:
|
||||||
def is_relative_to(self, other):
|
def is_relative_to(self, other):
|
||||||
"""Return True if the path is relative to another path or False.
|
"""Return True if the path is relative to another path or False.
|
||||||
"""
|
"""
|
||||||
if not isinstance(other, PurePath):
|
if not hasattr(other, 'with_segments'):
|
||||||
other = self.with_segments(other)
|
other = self.with_segments(other)
|
||||||
return other == self or other in self.parents
|
return other == self or other in self.parents
|
||||||
|
|
||||||
|
@ -545,7 +545,7 @@ class PurePath:
|
||||||
Return True if this path matches the given glob-style pattern. The
|
Return True if this path matches the given glob-style pattern. The
|
||||||
pattern is matched against the entire path.
|
pattern is matched against the entire path.
|
||||||
"""
|
"""
|
||||||
if not isinstance(pattern, PurePath):
|
if not hasattr(pattern, 'with_segments'):
|
||||||
pattern = self.with_segments(pattern)
|
pattern = self.with_segments(pattern)
|
||||||
if case_sensitive is None:
|
if case_sensitive is None:
|
||||||
case_sensitive = self.parser is posixpath
|
case_sensitive = self.parser is posixpath
|
||||||
|
@ -564,7 +564,7 @@ class PurePath:
|
||||||
is matched. The recursive wildcard '**' is *not* supported by this
|
is matched. The recursive wildcard '**' is *not* supported by this
|
||||||
method.
|
method.
|
||||||
"""
|
"""
|
||||||
if not isinstance(path_pattern, PurePath):
|
if not hasattr(path_pattern, 'with_segments'):
|
||||||
path_pattern = self.with_segments(path_pattern)
|
path_pattern = self.with_segments(path_pattern)
|
||||||
if case_sensitive is None:
|
if case_sensitive is None:
|
||||||
case_sensitive = self.parser is posixpath
|
case_sensitive = self.parser is posixpath
|
||||||
|
@ -1064,7 +1064,9 @@ class Path(PurePath):
|
||||||
Returns the new Path instance pointing to the target path.
|
Returns the new Path instance pointing to the target path.
|
||||||
"""
|
"""
|
||||||
os.rename(self, target)
|
os.rename(self, target)
|
||||||
return self.with_segments(target)
|
if not hasattr(target, 'with_segments'):
|
||||||
|
target = self.with_segments(target)
|
||||||
|
return target
|
||||||
|
|
||||||
def replace(self, target):
|
def replace(self, target):
|
||||||
"""
|
"""
|
||||||
|
@ -1077,7 +1079,9 @@ class Path(PurePath):
|
||||||
Returns the new Path instance pointing to the target path.
|
Returns the new Path instance pointing to the target path.
|
||||||
"""
|
"""
|
||||||
os.replace(self, target)
|
os.replace(self, target)
|
||||||
return self.with_segments(target)
|
if not hasattr(target, 'with_segments'):
|
||||||
|
target = self.with_segments(target)
|
||||||
|
return target
|
||||||
|
|
||||||
_copy_writer = property(LocalCopyWriter)
|
_copy_writer = property(LocalCopyWriter)
|
||||||
|
|
||||||
|
@ -1086,7 +1090,7 @@ class Path(PurePath):
|
||||||
"""
|
"""
|
||||||
Recursively copy this file or directory tree to the given destination.
|
Recursively copy this file or directory tree to the given destination.
|
||||||
"""
|
"""
|
||||||
if not hasattr(target, '_copy_writer'):
|
if not hasattr(target, 'with_segments'):
|
||||||
target = self.with_segments(target)
|
target = self.with_segments(target)
|
||||||
|
|
||||||
# Delegate to the target path's CopyWriter object.
|
# Delegate to the target path's CopyWriter object.
|
||||||
|
@ -1104,7 +1108,7 @@ class Path(PurePath):
|
||||||
name = self.name
|
name = self.name
|
||||||
if not name:
|
if not name:
|
||||||
raise ValueError(f"{self!r} has an empty name")
|
raise ValueError(f"{self!r} has an empty name")
|
||||||
elif hasattr(target_dir, '_copy_writer'):
|
elif hasattr(target_dir, 'with_segments'):
|
||||||
target = target_dir / name
|
target = target_dir / name
|
||||||
else:
|
else:
|
||||||
target = self.with_segments(target_dir, name)
|
target = self.with_segments(target_dir, name)
|
||||||
|
@ -1118,16 +1122,13 @@ class Path(PurePath):
|
||||||
"""
|
"""
|
||||||
# Use os.replace() if the target is os.PathLike and on the same FS.
|
# Use os.replace() if the target is os.PathLike and on the same FS.
|
||||||
try:
|
try:
|
||||||
target_str = os.fspath(target)
|
target = self.with_segments(target)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if not hasattr(target, '_copy_writer'):
|
|
||||||
target = self.with_segments(target_str)
|
|
||||||
ensure_different_files(self, target)
|
ensure_different_files(self, target)
|
||||||
try:
|
try:
|
||||||
os.replace(self, target_str)
|
return self.replace(target)
|
||||||
return target
|
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
if err.errno != EXDEV:
|
if err.errno != EXDEV:
|
||||||
raise
|
raise
|
||||||
|
@ -1143,7 +1144,7 @@ class Path(PurePath):
|
||||||
name = self.name
|
name = self.name
|
||||||
if not name:
|
if not name:
|
||||||
raise ValueError(f"{self!r} has an empty name")
|
raise ValueError(f"{self!r} has an empty name")
|
||||||
elif hasattr(target_dir, '_copy_writer'):
|
elif hasattr(target_dir, 'with_segments'):
|
||||||
target = target_dir / name
|
target = target_dir / name
|
||||||
else:
|
else:
|
||||||
target = self.with_segments(target_dir, name)
|
target = self.with_segments(target_dir, name)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
Apply type conversion consistently in :class:`pathlib.PurePath` and
|
||||||
|
:class:`~pathlib.Path` methods can accept a path object as an argument, such
|
||||||
|
as :meth:`~pathlib.PurePath.match` and :meth:`~pathlib.Path.rename`. The
|
||||||
|
argument is now converted to path object if it lacks a
|
||||||
|
:meth:`~pathlib.PurePath.with_segments` attribute, and not otherwise.
|
Loading…
Add table
Add a link
Reference in a new issue