8000 GH-101362: Optimise PurePath(PurePath(...)) by barneygale · Pull Request #101667 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

GH-101362: Optimise PurePath(PurePath(...)) #101667

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

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
GH-101362: Optimise PurePath(PurePath(...))
The previous `_parse_args()` method pulled the `_parts` out of any
supplied `PurePath` objects; these were subsequently joined in
`_from_parts()` using `os.path.join()`. This is actually a slower form of
joining than calling `fspath()` on the path object, because it doesn't take
advantage of the fact that the contents of `_parts` is normalized!

This reduces the time taken to run `PurePath("foo", "bar") by ~20%, and
the time taken to run `PurePath(p, "cheese")`, where
`p = PurePath("/foo", "bar", "baz")`, by ~40%.
  • Loading branch information
barneygale committed Feb 7, 2023
commit b01c352f06a7bb1291e4213079e821aa89fa8daa
36 changes: 11 additions & 25 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,14 @@ def _parse_parts(cls, parts):
sep = cls._flavour.sep
altsep = cls._flavour.altsep
path = cls._flavour.join(*parts)
if isinstance(path, str):
# Force-cast str subclasses to str (issue #21127)
path = str(path)
else:
raise TypeError(
"argument should be a str object or an os.PathLike "
"object returning str, not %r"
% type(path))
if altsep:
path = path.replace(altsep, sep)
drv, root, rel = cls._flavour.splitroot(path)
Expand All @@ -288,32 +296,10 @@ def _parse_parts(cls, parts):
parsed = [sys.intern(x) for x in unfiltered_parsed if x and x != '.']
return drv, root, parsed

@classmethod
def _parse_args(cls, args):
# This is useful when you don't want to create an instance, just
# canonicalize some constructor arguments.
parts = []
for a in args:
if isinstance(a, PurePath):
parts += a._parts
else:
a = os.fspath(a)
if isinstance(a, str):
# Force-cast str subclasses to str (issue #21127)
parts.append(str(a))
else:
raise TypeError(
"argument should be a str object or an os.PathLike "
"object returning str, not %r"
% type(a))
return cls._parse_parts(parts)

@classmethod
def _from_parts(cls, args):
# We need to call _parse_args on the instance, so as to get the
# right flavour.
self = object.__new__(cls)
drv, root, parts = self._parse_args(args)
drv, root, parts = self._parse_parts(args)
self._drv = drv
self._root = root
self._parts = parts
Expand Down Expand Up @@ -572,7 +558,7 @@ def joinpath(self, *args):
anchored).
"""
drv1, root1, parts1 = self._drv, self._root, self._parts
drv2, root2, parts2 = self._parse_args(args)
drv2, root2, parts2 = self._parse_parts(args)
if root2:
if not drv2 and drv1:
return self._from_parsed_parts(drv1, root2, [drv1 + root2] + parts2[1:])
Expand Down Expand Up @@ -664,7 +650,7 @@ def match(self, path_pattern):
return True

# Can't subclass os.PathLike from PurePath and keep the constructor
# optimizations in PurePath._parse_args().
# optimizations in PurePath.__slots__.
os.PathLike.register(PurePath)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Speed up :class:`pathlib.PurePath` construction by handling arguments more
uniformly. When a path argument is supplied, we use its string
representation rather than joining its parts with :func:`os.path.join`.
0