From 911df8d11f6c21c953469c4c2f71673ff3f420db Mon Sep 17 00:00:00 2001 From: barneygale Date: Sat, 9 Dec 2023 19:37:58 +0000 Subject: [PATCH 1/4] GH-112906: Fix performance regression in pathlib path initialisation This was caused by 76929fdeeb, specifically its use of `super()` and its packing/unpacking `*args`. --- Lib/pathlib/__init__.py | 4 ++-- Lib/pathlib/_abc.py | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index f4668ab3270e51..4f2dfd908cfe35 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -70,7 +70,7 @@ def __new__(cls, *args, **kwargs): cls = PureWindowsPath if os.name == 'nt' else PurePosixPath return object.__new__(cls) - def __init__(self, *args): + def _load_args(self, args): paths = [] for arg in args: if isinstance(arg, PurePath): @@ -90,7 +90,7 @@ def __init__(self, *args): "object where __fspath__ returns a str, " f"not {type(path).__name__!r}") paths.append(path) - super().__init__(*paths) + return paths def __reduce__(self): # Using the parts tuple helps share interned path parts diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 4808d0e61f7038..160840497f4065 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -206,10 +206,14 @@ class PurePathBase: ) pathmod = os.path - def __init__(self, *paths): - self._raw_paths = paths + def __init__(self, *args): + self._raw_paths = self._load_args(args) self._resolving = False + def _load_args(self, args): + # overridden in pathlib.PurePath + return args + def with_segments(self, *pathsegments): """Construct a new path object from any number of path-like objects. Subclasses may override this method to customize how new path objects From 4c327d7c81250dd15d8be6f1d7f51aaf3c4fcdea Mon Sep 17 00:00:00 2001 From: barneygale Date: Sat, 9 Dec 2023 23:02:31 +0000 Subject: [PATCH 2/4] Docstrings --- Lib/pathlib/__init__.py | 5 +++++ Lib/pathlib/_abc.py | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index 4f2dfd908cfe35..a7d85e90293bd3 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -71,6 +71,11 @@ def __new__(cls, *args, **kwargs): return object.__new__(cls) def _load_args(self, args): + """Type-checks and returns the given *args*. TypeError is raised if + any argument does not implement the os.PathLike interface, or + implements it but doesn't return a string. This method is called from + PurePathBase.__init__(). + """ paths = [] for arg in args: if isinstance(arg, PurePath): diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 160840497f4065..2ffd90212b723d 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -211,7 +211,11 @@ def __init__(self, *args): self._resolving = False def _load_args(self, args): - # overridden in pathlib.PurePath + """Returns the given *args* unchanged. Subclasses may override this + method to avoid adding an __init__() method that calls super() and + packs/unpacks arguments, which would introduce a noticeable slowdown. + This method is overridden in PurePath. + """ return args def with_segments(self, *pathsegments): From 7136b327e08bd74d3eee274a063066b321e92f06 Mon Sep 17 00:00:00 2001 From: barneygale Date: Sat, 9 Dec 2023 23:32:48 +0000 Subject: [PATCH 3/4] Greatly simplify patch (h/t Alex Waygood) --- Lib/pathlib/__init__.py | 10 +++------- Lib/pathlib/_abc.py | 12 ++---------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index a7d85e90293bd3..f3a220ec3745c2 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -70,12 +70,7 @@ def __new__(cls, *args, **kwargs): cls = PureWindowsPath if os.name == 'nt' else PurePosixPath return object.__new__(cls) - def _load_args(self, args): - """Type-checks and returns the given *args*. TypeError is raised if - any argument does not implement the os.PathLike interface, or - implements it but doesn't return a string. This method is called from - PurePathBase.__init__(). - """ + def __init__(self, *args): paths = [] for arg in args: if isinstance(arg, PurePath): @@ -95,7 +90,8 @@ def _load_args(self, args): "object where __fspath__ returns a str, " f"not {type(path).__name__!r}") paths.append(path) - return paths + self._raw_paths = paths + self._resolving = False def __reduce__(self): # Using the parts tuple helps share interned path parts diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 2ffd90212b723d..4808d0e61f7038 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -206,18 +206,10 @@ class PurePathBase: ) pathmod = os.path - def __init__(self, *args): - self._raw_paths = self._load_args(args) + def __init__(self, *paths): + self._raw_paths = paths self._resolving = False - def _load_args(self, args): - """Returns the given *args* unchanged. Subclasses may override this - method to avoid adding an __init__() method that calls super() and - packs/unpacks arguments, which would introduce a noticeable slowdown. - This method is overridden in PurePath. - """ - return args - def with_segments(self, *pathsegments): """Construct a new path object from any number of path-like objects. Subclasses may override this method to customize how new path objects From 3dbb483d958fb096208022e22910070ef4c4bac8 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sat, 9 Dec 2023 23:47:13 +0000 Subject: [PATCH 4/4] Update Lib/pathlib/__init__.py Co-authored-by: Alex Waygood --- Lib/pathlib/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/pathlib/__init__.py b/Lib/pathlib/__init__.py index f3a220ec3745c2..b020d2db350da8 100644 --- a/Lib/pathlib/__init__.py +++ b/Lib/pathlib/__init__.py @@ -90,6 +90,7 @@ def __init__(self, *args): "object where __fspath__ returns a str, " f"not {type(path).__name__!r}") paths.append(path) + # Avoid calling super().__init__, as an optimisation self._raw_paths = paths self._resolving = False