From b827144ff558f1f289d3a2dcc9a7768de5811261 Mon Sep 17 00:00:00 2001 From: barneygale Date: Mon, 4 Dec 2023 21:41:46 +0000 Subject: [PATCH 1/4] GH-112727: Speed up `pathlib.Path.absolute()` Use `_from_parsed_parts()` to create a pre-joined/pre-parsed path, rather than passing multiple arguments to `with_segments()` --- Lib/pathlib.py | 35 +++++++++++-------- ...-12-04-21-30-34.gh-issue-112727.jpgNRB.rst | 1 + 2 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-12-04-21-30-34.gh-issue-112727.jpgNRB.rst diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 81f75cd47ed087..c4b266650725a6 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -1415,21 +1415,28 @@ def absolute(self): """ if self.is_absolute(): return self - elif self.drive: - # There is a CWD on each drive-letter drive. - cwd = os.path.abspath(self.drive) - else: + elif self.root: cwd = os.getcwd() - # Fast path for "empty" paths, e.g. Path("."), Path("") or Path(). - # We pass only one argument to with_segments() to avoid the cost - # of joining, and we exploit the fact that getcwd() returns a - # fully-normalized string by storing it in _str. This is used to - # implement Path.cwd(). - if not self.root and not self._tail: - result = self.with_segments(cwd) - result._str = cwd - return result - return self.with_segments(cwd, self) + if self._tail: + cwd_drv = os.path.splitroot(cwd)[0] + return self._from_parsed_parts(cwd_drv, self.root, self._tail) + else: + cwd = os.path.abspath(self.drive) if self.drive else os.getcwd() + if self._tail: + # Optimization: use os.path.splitroot() rather than _parse_path(). + # This is possible because os.getcwd() returns a normalized path. + cwd_drv, cwd_root, cwd_tail = os.path.splitroot(cwd) + if cwd_tail: + cwd_tail = cwd_tail.split(self.pathmod.sep) + cwd_tail.extend(self._tail) + else: + cwd_tail = self._tail + return self._from_parsed_parts(cwd_drv, cwd_root, cwd_tail) + # Optimization: store the result of os.getcwd() as the normalized + # string representation of the path. + result = self.with_segments(cwd) + result._str = cwd + return result def resolve(self, strict=False): """ diff --git a/Misc/NEWS.d/next/Library/2023-12-04-21-30-34.gh-issue-112727.jpgNRB.rst b/Misc/NEWS.d/next/Library/2023-12-04-21-30-34.gh-issue-112727.jpgNRB.rst new file mode 100644 index 00000000000000..bbe7aae5732d9a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-04-21-30-34.gh-issue-112727.jpgNRB.rst @@ -0,0 +1 @@ +Speed up :meth:`pathlib.Path.absolute`. Patch by Barney Gale. From ca7119eb4d279a62731d6b72dfafd41e12e46c54 Mon Sep 17 00:00:00 2001 From: barneygale Date: Mon, 4 Dec 2023 22:11:41 +0000 Subject: [PATCH 2/4] Fix `WindowsPath('/').absolute()` --- Lib/pathlib.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index c4b266650725a6..965f449f1b92c4 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -1416,10 +1416,8 @@ def absolute(self): if self.is_absolute(): return self elif self.root: - cwd = os.getcwd() - if self._tail: - cwd_drv = os.path.splitroot(cwd)[0] - return self._from_parsed_parts(cwd_drv, self.root, self._tail) + cwd_drv = os.path.splitroot(os.getcwd())[0] + return self._from_parsed_parts(cwd_drv, self.root, self._tail) else: cwd = os.path.abspath(self.drive) if self.drive else os.getcwd() if self._tail: @@ -1432,11 +1430,12 @@ def absolute(self): else: cwd_tail = self._tail return self._from_parsed_parts(cwd_drv, cwd_root, cwd_tail) - # Optimization: store the result of os.getcwd() as the normalized - # string representation of the path. - result = self.with_segments(cwd) - result._str = cwd - return result + else: + # Optimization: store the result of os.getcwd() as the normalized + # string representation of the path. + result = self.with_segments(cwd) + result._str = cwd + return result def resolve(self, strict=False): """ From f3bcb4cfb639587fbcc85caf068b8dae9a2a5ff3 Mon Sep 17 00:00:00 2001 From: barneygale Date: Mon, 4 Dec 2023 22:40:05 +0000 Subject: [PATCH 3/4] Simplify overall patch --- Lib/pathlib.py | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 965f449f1b92c4..06f2ca28329be9 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -1416,26 +1416,28 @@ def absolute(self): if self.is_absolute(): return self elif self.root: - cwd_drv = os.path.splitroot(os.getcwd())[0] - return self._from_parsed_parts(cwd_drv, self.root, self._tail) + drive = os.path.splitroot(os.getcwd())[0] + return self._from_parsed_parts(drive, self.root, self._tail) + elif self.drive: + # There is a CWD on each drive-letter drive. + cwd = os.path.abspath(self.drive) else: - cwd = os.path.abspath(self.drive) if self.drive else os.getcwd() - if self._tail: - # Optimization: use os.path.splitroot() rather than _parse_path(). - # This is possible because os.getcwd() returns a normalized path. - cwd_drv, cwd_root, cwd_tail = os.path.splitroot(cwd) - if cwd_tail: - cwd_tail = cwd_tail.split(self.pathmod.sep) - cwd_tail.extend(self._tail) - else: - cwd_tail = self._tail - return self._from_parsed_parts(cwd_drv, cwd_root, cwd_tail) - else: - # Optimization: store the result of os.getcwd() as the normalized - # string representation of the path. - result = self.with_segments(cwd) - result._str = cwd - return result + cwd = os.getcwd() + if not self._tail: + # Fast path for "empty" paths, e.g. Path("."), Path("") or Path(). + # We pass only one argument to with_segments() to avoid the cost + # of joining, and we exploit the fact that getcwd() returns a + # fully-normalized string by storing it in _str. This is used to + # implement Path.cwd(). + result = self.with_segments(cwd) + result._str = cwd + return result + drive, root, rel = os.path.splitroot(cwd) + if not rel: + return self._from_parsed_parts(drive, root, self._tail) + tail = rel.split(self.pathmod.sep) + tail.extend(self._tail) + return self._from_parsed_parts(drive, root, tail) def resolve(self, strict=False): """ From 2ad781ef928b519964048760e0215f6c4e658ac9 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Mon, 4 Dec 2023 23:04:38 +0000 Subject: [PATCH 4/4] Update Lib/pathlib.py Co-authored-by: Alex Waygood --- Lib/pathlib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 06f2ca28329be9..8fb7490de619a4 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -1415,10 +1415,10 @@ def absolute(self): """ if self.is_absolute(): return self - elif self.root: + if self.root: drive = os.path.splitroot(os.getcwd())[0] return self._from_parsed_parts(drive, self.root, self._tail) - elif self.drive: + if self.drive: # There is a CWD on each drive-letter drive. cwd = os.path.abspath(self.drive) else: