Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-104996: Defer joining of pathlib.PurePath() arguments. #104999

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 27 additions & 17 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,10 @@ def _select_unique(paths):
yielded = set()
try:
for path in paths:
raw_path = path._raw_path
if raw_path not in yielded:
path_str = str(path)
if path_str not in yielded:
yield path
yielded.add(raw_path)
barneygale marked this conversation as resolved.
Show resolved Hide resolved
yielded.add(path_str)
finally:
yielded.clear()

Expand Down Expand Up @@ -247,9 +247,9 @@ class PurePath:
"""

__slots__ = (
# The `_raw_path` slot stores an unnormalized string path. This is set
# The `_raw_paths` slot stores unnormalized string paths. This is set
# in the `__init__()` method.
'_raw_path',
'_raw_paths',

# The `_drv`, `_root` and `_tail_cached` slots store parsed and
# normalized parts of the path. They are set when any of the `drive`,
Expand Down Expand Up @@ -306,10 +306,11 @@ def __init__(self, *args):
paths = []
for arg in args:
if isinstance(arg, PurePath):
path = arg._raw_path
if arg._flavour is ntpath and self._flavour is posixpath:
# GH-103631: Convert separators for backwards compatibility.
path = path.replace('\\', '/')
paths.extend(path.replace('\\', '/') for path in arg._raw_paths)
else:
paths.extend(arg._raw_paths)
else:
try:
path = os.fspath(arg)
Expand All @@ -320,13 +321,8 @@ 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 len(paths) == 0:
self._raw_path = ''
elif len(paths) == 1:
self._raw_path = paths[0]
else:
self._raw_path = self._flavour.join(*paths)
paths.append(path)
self._raw_paths = paths

def with_segments(self, *pathsegments):
"""Construct a new path object from any number of path-like objects.
Expand Down Expand Up @@ -356,7 +352,14 @@ def _parse_path(cls, path):
return drv, root, parsed

def _load_parts(self):
drv, root, tail = self._parse_path(self._raw_path)
paths = self._raw_paths
if len(paths) == 0:
path = ''
elif len(paths) == 1:
path = paths[0]
else:
path = self._flavour.join(*paths)
drv, root, tail = self._parse_path(path)
self._drv = drv
self._root = root
self._tail_cached = tail
Expand Down Expand Up @@ -687,10 +690,17 @@ def parents(self):
def is_absolute(self):
"""True if the path is absolute (has both a root and, if applicable,
a drive)."""
# ntpath.isabs() is defective - see GH-44626 .
if self._flavour is ntpath:
# ntpath.isabs() is defective - see GH-44626.
return bool(self.drive and self.root)
barneygale marked this conversation as resolved.
Show resolved Hide resolved
return self._flavour.isabs(self._raw_path)
elif self._flavour is posixpath:
# Optimization: work with raw paths on POSIX.
for path in self._raw_paths:
barneygale marked this conversation as resolved.
Show resolved Hide resolved
if path.startswith('/'):
return True
return False
else:
return self._flavour.isabs(str(self))

def is_reserved(self):
"""Return True if the path contains one of the special names reserved
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve performance of :class:`pathlib.PurePath` initialisation by
deferring joining of paths when multiple arguments are given.