From 081cbc25153e54ecbc260ab8b5c218564b7ec585 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Sun, 16 Aug 2020 15:24:26 -0700 Subject: [PATCH 01/20] bpo-41109: separate object creation by Path and PurePath member variables and properties from __new__() object creation --- Lib/pathlib.py | 50 +++++++++---- Lib/test/test_pathlib.py | 151 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 14 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 9f5e27b91178e6..315f2082d2eaa8 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -629,7 +629,7 @@ def __len__(self): def __getitem__(self, idx): if idx < 0 or idx >= len(self): raise IndexError(idx) - return self._pathcls._from_parsed_parts(self._drv, self._root, + return self._pathcls()._new_from_parsed_parts(self._drv, self._root, self._parts[:-idx - 1]) def __repr__(self): @@ -700,6 +700,9 @@ def _from_parts(cls, args, init=True): @classmethod def _from_parsed_parts(cls, drv, root, parts, init=True): + import warnings + warnings.warn('_from_parsed_parts() is deprecated, use _new_from_parsed_parts() instead', + DeprecationWarning, 2) self = object.__new__(cls) self._drv = drv self._root = root @@ -708,6 +711,25 @@ def _from_parsed_parts(cls, drv, root, parts, init=True): self._init() return self + def _new_from_parts(self, args, init=True): + obj = type(self)() + drv, root, parts = obj._parse_args(args) + obj._drv = drv + obj._root = root + obj._parts = parts + if init: + obj._init() + return obj + + def _new_from_parsed_parts(self, drv, root, parts, init=True): + obj = type(self)() + obj._drv = drv + obj._root = root + obj._parts = parts + if init: + obj._init() + return obj + @classmethod def _format_parsed_parts(cls, drv, root, parts): if drv or root: @@ -723,7 +745,7 @@ def _make_child(self, args): drv, root, parts = self._parse_args(args) drv, root, parts = self._flavour.join_parsed_parts( self._drv, self._root, self._parts, drv, root, parts) - return self._from_parsed_parts(drv, root, parts) + return self._new_from_parsed_parts(drv, root, parts) def __str__(self): """Return the string representation of the path, suitable for @@ -867,7 +889,7 @@ def with_name(self, name): if (not name or name[-1] in [self._flavour.sep, self._flavour.altsep] or drv or root or len(parts) != 1): raise ValueError("Invalid name %r" % (name)) - return self._from_parsed_parts(self._drv, self._root, + return self._new_from_parsed_parts(self._drv, self._root, self._parts[:-1] + [name]) def with_stem(self, stem): @@ -892,8 +914,8 @@ def with_suffix(self, suffix): name = name + suffix else: name = name[:-len(old_suffix)] + suffix - return self._from_parsed_parts(self._drv, self._root, - self._parts[:-1] + [name]) + return self._new_from_parsed_parts(self._drv, self._root, + self._parts[:-1] + [name]) def relative_to(self, *other): """Return the relative path to another path identified by the passed @@ -925,8 +947,8 @@ def relative_to(self, *other): raise ValueError("{!r} is not in the subpath of {!r}" " OR one path is relative and the other is absolute." .format(str(self), str(formatted))) - return self._from_parsed_parts('', root if n == 1 else '', - abs_parts[n:]) + return self._new_from_parsed_parts('', root if n == 1 else '', + abs_parts[n:]) def is_relative_to(self, *other): """Return True if the path is relative to another path or False. @@ -965,7 +987,7 @@ def __truediv__(self, key): def __rtruediv__(self, key): try: - return self._from_parts([key] + self._parts) + return self._new_from_parts([key] + self._parts) except TypeError: return NotImplemented @@ -977,7 +999,7 @@ def parent(self): parts = self._parts if len(parts) == 1 and (drv or root): return self - return self._from_parsed_parts(drv, root, parts[:-1]) + return self._new_from_parsed_parts(drv, root, parts[:-1]) @property def parents(self): @@ -1085,7 +1107,7 @@ def _make_child_relpath(self, part): # This is an optimization used for dir walking. `part` must be # a single part relative to this path. parts = self._parts + [part] - return self._from_parsed_parts(self._drv, self._root, parts) + return self._new_from_parsed_parts(self._drv, self._root, parts) def __enter__(self): return self @@ -1188,7 +1210,7 @@ def absolute(self): return self # FIXME this must defer to the specific flavour (and, under Windows, # use nt._getfullpathname()) - obj = self._from_parts([os.getcwd()] + self._parts, init=False) + obj = self._new_from_parts([os.getcwd()] + self._parts, init=False) obj._init(template=self) return obj @@ -1206,7 +1228,7 @@ def resolve(self, strict=False): s = str(self.absolute()) # Now we have no symlinks in the path, it's safe to normalize it. normed = self._flavour.pathmod.normpath(s) - obj = self._from_parts((normed,), init=False) + obj = self._new_from_parts((normed,), init=False) obj._init(template=self) return obj @@ -1276,7 +1298,7 @@ def readlink(self): Return the path to which the symbolic link points. """ path = self._accessor.readlink(self) - obj = self._from_parts((path,), init=False) + obj = self._new_from_parts((path,), init=False) obj._init(template=self) return obj @@ -1541,7 +1563,7 @@ def expanduser(self): if (not (self._drv or self._root) and self._parts and self._parts[0][:1] == '~'): homedir = self._flavour.gethomedir(self._parts[0][1:]) - return self._from_parts([homedir] + self._parts[1:]) + return self._new_from_parts([homedir] + self._parts[1:]) return self diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 04f7c3d86671bf..db319b9bae05df 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2585,5 +2585,156 @@ def test_rtruediv(self): 10 / pathlib.PurePath("test") +class PurePosixSubclassNewAndInitTest(unittest.TestCase): + """ + Test that the __init__() and __new__() functions of subclasses + of PurePosixPath get called when PosixPath functions + and properties instantiate new objects of the subclass. + """ + cls = pathlib.PurePosixPath + + class ASubclass(cls): + new_called = False + init_called = False + + def __new__(cls, *args, **kwargs): + cls.new_called = True + return super().__new__(cls, *args, **kwargs) + + def __init__(self, *args, **kwargs): + self.init_called = True + super().__init__() + + def validate_object(self, path, value): + self.assertIs(type(path), self.ASubclass) + self.assertTrue(path.new_called) + self.assertTrue(path.init_called) + self.assertEqual(str(path), value) + + def test_class_initialization(self): + self.validate_object(self.ASubclass('a/b/c.foo'), 'a/b/c.foo') + + def test_joinpath(self): + self.validate_object(self.ASubclass('a/b/c.foo').parent.joinpath('d/e'), + 'a/b/d/e') + + def test_parent(self): + self.validate_object(self.ASubclass('a/b/c.foo').parent, 'a/b') + + def test_parents(self): + path = self.ASubclass('a/b/c.foo') + self.validate_object(path.parents[0], 'a/b') + self.validate_object(path.parents[1], 'a') + + def test_relative_to(self): + self.validate_object(self.ASubclass('a/b/c.foo').relative_to('a'), + 'b/c.foo') + + def test_rtruediv(self): + self.validate_object('left' / self.ASubclass('test'), 'left/test') + + def test_truediv(self): + path = self.ASubclass('a/b/c.foo') + self.validate_object(path.parent / 'd' / path, 'a/b/d/a/b/c.foo') + + def test_with_name(self): + self.validate_object(self.ASubclass('a/b/c.foo').with_name('bar'), + 'a/b/bar') + + def test_with_suffix(self): + self.validate_object(self.ASubclass('a/b/c.foo').with_suffix('.bar'), + 'a/b/c.bar') + + +@only_posix +class PosixPathSubclassNewAndInitTest(unittest.TestCase): + """ + Test that the __init__() and __new__() functions of subclasses + of PosixPath get called when PosixPath functions + and properties instantiate new objects of the subclass. + """ + cls = pathlib.PosixPath + + class ASubclass(cls): + new_called = False + init_called = False + + def __new__(cls, *args, **kwargs): + cls.new_called = True + return super().__new__(cls, *args, **kwargs) + + def __init__(self, *args, **kwargs): + self.init_called = True + super().__init__() + + def validate_object(self, path, value): + self.assertIs(type(path), self.ASubclass) + self.assertTrue(path.new_called) + self.assertTrue(path.init_called) + self.assertEqual(str(path), value) + + def test_class_initialization(self): + self.validate_object(self.ASubclass('a/b/c.foo'), 'a/b/c.foo') + + def test_absolute(self): + relpath = 'a/b/c.foo' + self.validate_object(self.ASubclass(relpath).absolute(), + join(os.getcwd(), relpath)) + + def test_expanduser(self): + import_helper.import_module('pwd') + import pwd + pwdent = pwd.getpwuid(os.getuid()) + userhome = pwdent.pw_dir.rstrip('/') or '/' + path = self.ASubclass('~/Documents') + with os_helper.EnvironmentVarGuard() as env: + env.pop('HOME', None) + self.validate_object(path.expanduser(), join(userhome, 'Documents')) + + def test_glob_rglob_iterdir(self): + # create a file in a temp directory + d = tempfile.mkdtemp() + filename = 'fileA' + with open(join(d, filename), 'wb') as f: + f.write(b"this is file A\n") + + # create an Asubclass object for testing + path = self.ASubclass(d) + self.validate_object(path, d) + + # verify __new__ and __init__ are called + # in the subclass for each created instance + # and the subclass value is correct + cases = [(path.glob, 'f*', join(d, filename)), # _WildcardSelector + (path.glob, 'fileA', join(d, filename)), # _PreciseSelector + (path.glob, '**', d), # _RecursiveWildcardSelector + (path.rglob, 'f*', join(d, filename)), + (path.iterdir, None, join(d, filename))] + + for func, param, value in cases: + n = 0 + for name in func() if param is None else func(param): + self.validate_object(name, value) + n += 1 + self.assertEqual(n, 1) + + def test_resolve(self): + relpath = 'a/b/c.foo' + self.validate_object(self.ASubclass(relpath).resolve(), + join(os.getcwd(), relpath)) + + @os_helper.skip_unless_symlink + def test_readlink(self): + # create a file in a temp directory + d = tempfile.mkdtemp() + with open(join(d, 'fileA'), 'wb') as f: + f.write(b"this is file A\n") + # create a symlink to the file + os.symlink(join(d, 'fileA'), join(d, 'linkA')) + + path = (self.ASubclass(d) / 'linkA').readlink() + self.validate_object(path, join(d, 'fileA')) + + if __name__ == "__main__": unittest.main() From 0e4d53e0c879dab51904f010af8d2789d6e6ac96 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Sun, 16 Aug 2020 17:58:59 -0700 Subject: [PATCH 02/20] bpo-41109: Windows PurePath tests --- Lib/test/test_pathlib.py | 61 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index db319b9bae05df..c3bb3c9f4e6edb 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2646,6 +2646,67 @@ def test_with_suffix(self): 'a/b/c.bar') +class PureWindowsSubclassNewAndInitTest(unittest.TestCase): + """ + Test that the __init__() and __new__() functions of subclasses + of PurePosixPath get called when PosixPath functions + and properties instantiate new objects of the subclass. + """ + cls = pathlib.PurePosixPath + + class ASubclass(cls): + new_called = False + init_called = False + + def __new__(cls, *args, **kwargs): + cls.new_called = True + return super().__new__(cls, *args, **kwargs) + + def __init__(self, *args, **kwargs): + self.init_called = True + super().__init__() + + def validate_object(self, path, value): + self.assertIs(type(path), self.ASubclass) + self.assertTrue(path.new_called) + self.assertTrue(path.init_called) + self.assertEqual(str(path), value) + + def test_class_initialization(self): + self.validate_object(self.ASubclass('c:/a/b/c.foo'), 'c:/a/b/c.foo') + + def test_joinpath(self): + self.validate_object(self.ASubclass('c:/a/b/c.foo').parent.joinpath('d/e'), + 'c:/a/b/d/e') + + def test_parent(self): + self.validate_object(self.ASubclass('c:/a/b/c.foo').parent, 'c:/a/b') + + def test_parents(self): + path = self.ASubclass('c:/a/b/c.foo') + self.validate_object(path.parents[0], 'c:/a/b') + self.validate_object(path.parents[1], 'c:/a') + + def test_relative_to(self): + self.validate_object(self.ASubclass('c:/a/b/c.foo').relative_to('c:/a'), + 'b/c.foo') + + def test_rtruediv(self): + self.validate_object('c:/left' / self.ASubclass('test'), 'c:/left/test') + + def test_truediv(self): + path = self.ASubclass('c:/a/b/c.foo') + self.validate_object(path.parent / 'd', 'c:/a/b/d') + + def test_with_name(self): + self.validate_object(self.ASubclass('c:/a/b/c.foo').with_name('bar'), + 'c:/a/b/bar') + + def test_with_suffix(self): + self.validate_object(self.ASubclass('c:/a/b/c.foo').with_suffix('.bar'), + 'c:/a/b/c.bar') + + @only_posix class PosixPathSubclassNewAndInitTest(unittest.TestCase): """ From 53643f48541615f9c9c51af386df1495c2dd1108 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Sun, 16 Aug 2020 19:07:38 -0700 Subject: [PATCH 03/20] bpo-41109: use correct class in PureWindowsSubclassNewAndInitTest --- Lib/test/test_pathlib.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index c3bb3c9f4e6edb..2de7f55e8c6300 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2652,7 +2652,7 @@ class PureWindowsSubclassNewAndInitTest(unittest.TestCase): of PurePosixPath get called when PosixPath functions and properties instantiate new objects of the subclass. """ - cls = pathlib.PurePosixPath + cls = pathlib.PureWindowsPath class ASubclass(cls): new_called = False @@ -2673,38 +2673,38 @@ def validate_object(self, path, value): self.assertEqual(str(path), value) def test_class_initialization(self): - self.validate_object(self.ASubclass('c:/a/b/c.foo'), 'c:/a/b/c.foo') + self.validate_object(self.ASubclass('c:\\a\\b\\c.foo'), 'c:\\a\\b\\c.foo') def test_joinpath(self): - self.validate_object(self.ASubclass('c:/a/b/c.foo').parent.joinpath('d/e'), - 'c:/a/b/d/e') + self.validate_object(self.ASubclass('c:\\a\\b\\c.foo').parent.joinpath('d\\e'), + 'c:\\a\\b\\d\\e') def test_parent(self): - self.validate_object(self.ASubclass('c:/a/b/c.foo').parent, 'c:/a/b') + self.validate_object(self.ASubclass('c:\\a\\b\\c.foo').parent, 'c:\\a\\b') def test_parents(self): - path = self.ASubclass('c:/a/b/c.foo') - self.validate_object(path.parents[0], 'c:/a/b') - self.validate_object(path.parents[1], 'c:/a') + path = self.ASubclass('c:\\a\\b\\c.foo') + self.validate_object(path.parents[0], 'c:\\a\\b') + self.validate_object(path.parents[1], 'c:\\a') def test_relative_to(self): - self.validate_object(self.ASubclass('c:/a/b/c.foo').relative_to('c:/a'), - 'b/c.foo') + self.validate_object(self.ASubclass('c:\\a\\b\\c.foo').relative_to('c:\\a'), + 'b\\c.foo') def test_rtruediv(self): - self.validate_object('c:/left' / self.ASubclass('test'), 'c:/left/test') + self.validate_object('c:\\left' / self.ASubclass('test'), 'c:\\left\\test') def test_truediv(self): - path = self.ASubclass('c:/a/b/c.foo') - self.validate_object(path.parent / 'd', 'c:/a/b/d') + path = self.ASubclass('c:\\a\\b\\c.foo') + self.validate_object(path.parent / 'd', 'c:\\a\\b\\d') def test_with_name(self): - self.validate_object(self.ASubclass('c:/a/b/c.foo').with_name('bar'), - 'c:/a/b/bar') + self.validate_object(self.ASubclass('c:\\a\\b\\c.foo').with_name('bar'), + 'c:\\a\\b\\bar') def test_with_suffix(self): - self.validate_object(self.ASubclass('c:/a/b/c.foo').with_suffix('.bar'), - 'c:/a/b/c.bar') + self.validate_object(self.ASubclass('c:\\a\\b\\c.foo').with_suffix('.bar'), + 'c:\\a\\b\\c.bar') @only_posix From c418958c9a2843dc785705a957b5739eeff3be50 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Sun, 16 Aug 2020 20:35:25 -0700 Subject: [PATCH 04/20] bpo-41109: fix comment --- Lib/test/test_pathlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 2de7f55e8c6300..a6bc7d7c29f25a 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2649,7 +2649,7 @@ def test_with_suffix(self): class PureWindowsSubclassNewAndInitTest(unittest.TestCase): """ Test that the __init__() and __new__() functions of subclasses - of PurePosixPath get called when PosixPath functions + of PureWindowsPath get called when PosixPath functions and properties instantiate new objects of the subclass. """ cls = pathlib.PureWindowsPath From 34683c42664d2f46f712a7e81a66fc9ae704b470 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Sun, 16 Aug 2020 21:40:50 -0700 Subject: [PATCH 05/20] bpo-41109: fix class names and comments --- Lib/test/test_pathlib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index a6bc7d7c29f25a..f9adbf9c0d1c15 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2585,10 +2585,10 @@ def test_rtruediv(self): 10 / pathlib.PurePath("test") -class PurePosixSubclassNewAndInitTest(unittest.TestCase): +class PurePosixPathSubclassNewAndInitTest(unittest.TestCase): """ Test that the __init__() and __new__() functions of subclasses - of PurePosixPath get called when PosixPath functions + of PurePosixPath get called when PurePosixPath functions and properties instantiate new objects of the subclass. """ cls = pathlib.PurePosixPath @@ -2646,10 +2646,10 @@ def test_with_suffix(self): 'a/b/c.bar') -class PureWindowsSubclassNewAndInitTest(unittest.TestCase): +class PureWindowsPathSubclassNewAndInitTest(unittest.TestCase): """ Test that the __init__() and __new__() functions of subclasses - of PureWindowsPath get called when PosixPath functions + of PureWindowsPath get called when PureWindowsPath functions and properties instantiate new objects of the subclass. """ cls = pathlib.PureWindowsPath From 19e0a405b9be3022b4be40120558f518895ddcf0 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Sun, 16 Aug 2020 23:47:23 -0700 Subject: [PATCH 06/20] bpo-41109: WindowsPath test suite --- Lib/test/test_pathlib.py | 95 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index f9adbf9c0d1c15..fef7683284c672 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2797,5 +2797,100 @@ def test_readlink(self): self.validate_object(path, join(d, 'fileA')) +@only_nt +class WindowsPathSubclassNewAndInitTest(unittest.TestCase): + """ + Test that the __init__() and __new__() functions of subclasses + of WindowsPath get called when WindowsPath functions + and properties instantiate new objects of the subclass. + """ + cls = pathlib.WindowsPath + + class ASubclass(cls): + new_called = False + init_called = False + + def __new__(cls, *args, **kwargs): + cls.new_called = True + return super().__new__(cls, *args, **kwargs) + + def __init__(self, *args, **kwargs): + self.init_called = True + super().__init__() + + def validate_object(self, path, value): + self.assertIs(type(path), self.ASubclass) + self.assertTrue(path.new_called) + self.assertTrue(path.init_called) + self.assertEqual(str(path), value) + + def test_class_initialization(self): + self.validate_object(self.ASubclass('c:\\a\\b\\c.foo'), 'c:\\a\\b\\c.foo') + + def test_absolute(self): + relpath = 'a\\b\\c.foo' + self.validate_object(self.ASubclass(relpath).absolute(), + join(os.getcwd(), relpath)) + + def test_expanduser(self): + import_helper.import_module('pwd') + import pwd + pwdent = pwd.getpwuid(os.getuid()) + userhome = pwdent.pw_dir.rstrip('\\') or '\\' + path = self.ASubclass('~\\Documents') + with os_helper.EnvironmentVarGuard() as env: + env.pop('HOME', None) + self.validate_object(path.expanduser(), join(userhome, 'Documents')) + + def test_glob_rglob_iterdir(self): + # create a file in a temp directory + d = tempfile.mkdtemp() + filename = 'fileA' + with open(join(d, filename), 'wb') as f: + f.write(b"this is file A\n") + + # create an Asubclass object for testing + path = self.ASubclass(d) + self.validate_object(path, d) + + # verify __new__ and __init__ are called + # in the subclass for each created instance + # and the subclass value is correct + cases = [(path.glob, 'f*', join(d, filename)), # _WildcardSelector + (path.glob, 'fileA', join(d, filename)), # _PreciseSelector + (path.glob, '**', d), # _RecursiveWildcardSelector + (path.rglob, 'f*', join(d, filename)), + (path.iterdir, None, join(d, filename))] + + for func, param, value in cases: + n = 0 + for name in func() if param is None else func(param): + self.validate_object(name, value) + n += 1 + self.assertEqual(n, 1) + + def test_resolve(self): + # create a file in a temp directory + d = tempfile.mkdtemp() + filename = 'fileA' + with open(join(d, filename), 'wb') as f: + f.write(b"this is file A\n") + + self.validate_object(self.ASubclass(d, filename).resolve(strict=True), + join(d, filename)) + + @os_helper.skip_unless_symlink + def test_readlink(self): + # create a file in a temp directory + d = tempfile.mkdtemp() + with open(join(d, 'fileA'), 'wb') as f: + f.write(b"this is file A\n") + # create a symlink to the file + os.symlink('fileA', join(d, 'linkA')) + + path = self.ASubclass(d, 'linkA').readlink() + self.validate_object(path, 'fileA') + + if __name__ == "__main__": unittest.main() From 1853c6da52cebb99554245f1ab14140492360175 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Mon, 17 Aug 2020 14:15:00 -0700 Subject: [PATCH 07/20] bpo-41109: add __init__() to Path and PurePath --- Lib/pathlib.py | 19 +++++++++++++++++-- Lib/test/test_pathlib.py | 8 ++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 315f2082d2eaa8..bb8b5a303122c9 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -658,7 +658,15 @@ def __new__(cls, *args): """ if cls is PurePath: cls = PureWindowsPath if os.name == 'nt' else PurePosixPath - return cls._from_parts(args) + return object.__new__(cls) + + def __init__(self, *args, init=True): + drv, root, parts = self._parse_args(args) + self._drv = drv + self._root = root + self._parts = parts + if init: + self._init() def __reduce__(self): # Using the parts tuple helps share interned path parts @@ -687,6 +695,9 @@ def _parse_args(cls, args): @classmethod def _from_parts(cls, args, init=True): + import warnings + warnings.warn('_from_parsed_parts() is deprecated, use _new_from_parsed_parts() instead', + DeprecationWarning, 2) # We need to call _parse_args on the instance, so as to get the # right flavour. self = object.__new__(cls) @@ -1088,12 +1099,16 @@ def __new__(cls, *args, **kwargs): if cls is Path: cls = WindowsPath if os.name == 'nt' else PosixPath self = cls._from_parts(args, init=False) + self = object.__new__(cls) if not self._flavour.is_supported: raise NotImplementedError("cannot instantiate %r on your system" % (cls.__name__,)) - self._init() return self + def __init__(self, *args, **kwargs): + super().__init__(*args, init=False) + self._init() + def _init(self, # Private non-constructor arguments template=None, diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index fef7683284c672..bb08f352baea47 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2603,7 +2603,7 @@ def __new__(cls, *args, **kwargs): def __init__(self, *args, **kwargs): self.init_called = True - super().__init__() + super().__init__(*args, **kwargs) def validate_object(self, path, value): self.assertIs(type(path), self.ASubclass) @@ -2664,7 +2664,7 @@ def __new__(cls, *args, **kwargs): def __init__(self, *args, **kwargs): self.init_called = True - super().__init__() + super().__init__(*args, **kwargs) def validate_object(self, path, value): self.assertIs(type(path), self.ASubclass) @@ -2726,7 +2726,7 @@ def __new__(cls, *args, **kwargs): def __init__(self, *args, **kwargs): self.init_called = True - super().__init__() + super().__init__(*args, **kwargs) def validate_object(self, path, value): self.assertIs(type(path), self.ASubclass) @@ -2816,7 +2816,7 @@ def __new__(cls, *args, **kwargs): def __init__(self, *args, **kwargs): self.init_called = True - super().__init__() + super().__init__(*args, **kwargs) def validate_object(self, path, value): self.assertIs(type(path), self.ASubclass) From 17c141cb946418adcc70ee19e8d7e8129126cfaa Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Mon, 17 Aug 2020 14:30:32 -0700 Subject: [PATCH 08/20] bpo-41109: remove extraneous **kwargs parameter from Path.__init__() --- Lib/pathlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index bb8b5a303122c9..64f0039c41b971 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -1105,7 +1105,7 @@ def __new__(cls, *args, **kwargs): % (cls.__name__,)) return self - def __init__(self, *args, **kwargs): + def __init__(self, *args): super().__init__(*args, init=False) self._init() From 505cab0da17b6e426d09397d91b6d661b5410e96 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Mon, 17 Aug 2020 16:40:22 -0700 Subject: [PATCH 09/20] bpo-41109: remove extraneous whitespace --- Lib/pathlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 64f0039c41b971..5c87e1c3d93cb7 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -666,7 +666,7 @@ def __init__(self, *args, init=True): self._root = root self._parts = parts if init: - self._init() + self._init() def __reduce__(self): # Using the parts tuple helps share interned path parts From c47bf06c53cf334a0978ba7bdddd4edb29bd1f5f Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Mon, 17 Aug 2020 18:24:16 -0700 Subject: [PATCH 10/20] bpo-41109: remove double-initialization in Path.__new__() --- Lib/pathlib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 5c87e1c3d93cb7..e5ca1c430e5286 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -1098,7 +1098,6 @@ class Path(PurePath): def __new__(cls, *args, **kwargs): if cls is Path: cls = WindowsPath if os.name == 'nt' else PosixPath - self = cls._from_parts(args, init=False) self = object.__new__(cls) if not self._flavour.is_supported: raise NotImplementedError("cannot instantiate %r on your system" From 6607e77b68d9cc047608198d4b25bdbf7935e5ca Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Mon, 17 Aug 2020 20:59:14 -0700 Subject: [PATCH 11/20] bpo-41109: no longer need to call _init() from PurePath.__init__() --- Lib/pathlib.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index e5ca1c430e5286..886c17b2ca4324 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -660,13 +660,11 @@ def __new__(cls, *args): cls = PureWindowsPath if os.name == 'nt' else PurePosixPath return object.__new__(cls) - def __init__(self, *args, init=True): + def __init__(self, *args): drv, root, parts = self._parse_args(args) self._drv = drv self._root = root self._parts = parts - if init: - self._init() def __reduce__(self): # Using the parts tuple helps share interned path parts @@ -1105,7 +1103,7 @@ def __new__(cls, *args, **kwargs): return self def __init__(self, *args): - super().__init__(*args, init=False) + super().__init__(*args) self._init() def _init(self, From cf6afb073b6b5691838167ce9e3dbf8fbb32e996 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Tue, 18 Aug 2020 11:58:20 -0700 Subject: [PATCH 12/20] bpo-41109: reorganize tests using a common base class for common tests --- Lib/test/test_pathlib.py | 190 +++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 107 deletions(-) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index bb08f352baea47..bd3ece49ae49e0 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2707,8 +2707,88 @@ def test_with_suffix(self): 'c:\\a\\b\\c.bar') +class _BasePathSubclassNewAndInitTest(object): + """ + Test that the __init__() and __new__() functions of subclasses + of Path get called when Path functions + and properties instantiate new objects of the subclass. + """ + def setUp(self): + def cleanup(): + os_helper.rmtree(BASE) + self.addCleanup(cleanup) + os.mkdir(BASE) + with open(join(BASE, 'fileA'), 'wb') as f: + f.write(b"this is file A\n") + + def validate_object(self, path, value): + self.assertIs(type(path), self.ASubclass) + self.assertTrue(path.new_called) + self.assertTrue(path.init_called) + self.assertEqual(str(path), value) + + def test_glob(self): + d = BASE + filename = 'fileA' + path = self.ASubclass(d) + self.validate_object(path, d) + cases = [(path.glob, 'f*', join(d, filename)), # _WildcardSelector + (path.glob, 'fileA', join(d, filename)), # _PreciseSelector + (path.glob, '**', d)] # _RecursiveWildcardSelector + + for func, param, value in cases: + n = 0 + for name in func(param): + self.validate_object(name, value) + n += 1 + self.assertEqual(n, 1) + + def test_rglob(self): + d = BASE + filename = 'fileA' + path = self.ASubclass(d) + self.validate_object(path, d) + n = 0 + for name in path.rglob('f*'): + self.validate_object(name, join(d, filename)) + n += 1 + self.assertEqual(n, 1) + + def test_iterdir(self): + d = BASE + filename = 'fileA' + path = self.ASubclass(d) + self.validate_object(path, d) + n = 0 + for name in path.iterdir(): + self.validate_object(name, join(d, filename)) + n += 1 + self.assertEqual(n, 1) + + @os_helper.skip_unless_symlink + def test_readlink(self): + d = BASE + filename = 'fileA' + linkname = 'linkA' + + # create a symlink to the file + os.symlink(join(d, filename), join(d, linkname)) + + path = (self.ASubclass(d) / linkname).readlink() + self.validate_object(path, join(d, filename)) + os.unlink(path) + + def test_resolve(self): + # create a file in a temp directory + d = BASE + filename = 'fileA' + self.validate_object(self.ASubclass(d, filename).resolve(strict=True), + join(d, filename)) + + @only_posix -class PosixPathSubclassNewAndInitTest(unittest.TestCase): +class PosixPathSubclassNewAndInitTest(_BasePathSubclassNewAndInitTest, + unittest.TestCase): """ Test that the __init__() and __new__() functions of subclasses of PosixPath get called when PosixPath functions @@ -2728,12 +2808,6 @@ def __init__(self, *args, **kwargs): self.init_called = True super().__init__(*args, **kwargs) - def validate_object(self, path, value): - self.assertIs(type(path), self.ASubclass) - self.assertTrue(path.new_called) - self.assertTrue(path.init_called) - self.assertEqual(str(path), value) - def test_class_initialization(self): self.validate_object(self.ASubclass('a/b/c.foo'), 'a/b/c.foo') @@ -2752,53 +2826,10 @@ def test_expanduser(self): env.pop('HOME', None) self.validate_object(path.expanduser(), join(userhome, 'Documents')) - def test_glob_rglob_iterdir(self): - # create a file in a temp directory - d = tempfile.mkdtemp() - filename = 'fileA' - with open(join(d, filename), 'wb') as f: - f.write(b"this is file A\n") - - # create an Asubclass object for testing - path = self.ASubclass(d) - self.validate_object(path, d) - - # verify __new__ and __init__ are called - # in the subclass for each created instance - # and the subclass value is correct - cases = [(path.glob, 'f*', join(d, filename)), # _WildcardSelector - (path.glob, 'fileA', join(d, filename)), # _PreciseSelector - (path.glob, '**', d), # _RecursiveWildcardSelector - (path.rglob, 'f*', join(d, filename)), - (path.iterdir, None, join(d, filename))] - - for func, param, value in cases: - n = 0 - for name in func() if param is None else func(param): - self.validate_object(name, value) - n += 1 - self.assertEqual(n, 1) - - def test_resolve(self): - relpath = 'a/b/c.foo' - self.validate_object(self.ASubclass(relpath).resolve(), - join(os.getcwd(), relpath)) - - @os_helper.skip_unless_symlink - def test_readlink(self): - # create a file in a temp directory - d = tempfile.mkdtemp() - with open(join(d, 'fileA'), 'wb') as f: - f.write(b"this is file A\n") - # create a symlink to the file - os.symlink(join(d, 'fileA'), join(d, 'linkA')) - - path = (self.ASubclass(d) / 'linkA').readlink() - self.validate_object(path, join(d, 'fileA')) - @only_nt -class WindowsPathSubclassNewAndInitTest(unittest.TestCase): +class WindowsPathSubclassNewAndInitTest(_BasePathSubclassNewAndInitTest, + unittest.TestCase): """ Test that the __init__() and __new__() functions of subclasses of WindowsPath get called when WindowsPath functions @@ -2818,12 +2849,6 @@ def __init__(self, *args, **kwargs): self.init_called = True super().__init__(*args, **kwargs) - def validate_object(self, path, value): - self.assertIs(type(path), self.ASubclass) - self.assertTrue(path.new_called) - self.assertTrue(path.init_called) - self.assertEqual(str(path), value) - def test_class_initialization(self): self.validate_object(self.ASubclass('c:\\a\\b\\c.foo'), 'c:\\a\\b\\c.foo') @@ -2842,55 +2867,6 @@ def test_expanduser(self): env.pop('HOME', None) self.validate_object(path.expanduser(), join(userhome, 'Documents')) - def test_glob_rglob_iterdir(self): - # create a file in a temp directory - d = tempfile.mkdtemp() - filename = 'fileA' - with open(join(d, filename), 'wb') as f: - f.write(b"this is file A\n") - - # create an Asubclass object for testing - path = self.ASubclass(d) - self.validate_object(path, d) - - # verify __new__ and __init__ are called - # in the subclass for each created instance - # and the subclass value is correct - cases = [(path.glob, 'f*', join(d, filename)), # _WildcardSelector - (path.glob, 'fileA', join(d, filename)), # _PreciseSelector - (path.glob, '**', d), # _RecursiveWildcardSelector - (path.rglob, 'f*', join(d, filename)), - (path.iterdir, None, join(d, filename))] - - for func, param, value in cases: - n = 0 - for name in func() if param is None else func(param): - self.validate_object(name, value) - n += 1 - self.assertEqual(n, 1) - - def test_resolve(self): - # create a file in a temp directory - d = tempfile.mkdtemp() - filename = 'fileA' - with open(join(d, filename), 'wb') as f: - f.write(b"this is file A\n") - - self.validate_object(self.ASubclass(d, filename).resolve(strict=True), - join(d, filename)) - - @os_helper.skip_unless_symlink - def test_readlink(self): - # create a file in a temp directory - d = tempfile.mkdtemp() - with open(join(d, 'fileA'), 'wb') as f: - f.write(b"this is file A\n") - # create a symlink to the file - os.symlink('fileA', join(d, 'linkA')) - - path = self.ASubclass(d, 'linkA').readlink() - self.validate_object(path, 'fileA') - if __name__ == "__main__": unittest.main() From d25fdd75359ad269b03c08d0b2a15dae134c3bfa Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Tue, 18 Aug 2020 12:30:59 -0700 Subject: [PATCH 13/20] bpo-41109: fix readlink test for Windows --- Lib/test/test_pathlib.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index bd3ece49ae49e0..8f01277238c865 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2769,14 +2769,14 @@ def test_iterdir(self): def test_readlink(self): d = BASE filename = 'fileA' - linkname = 'linkA' - - # create a symlink to the file - os.symlink(join(d, filename), join(d, linkname)) - - path = (self.ASubclass(d) / linkname).readlink() - self.validate_object(path, join(d, filename)) - os.unlink(path) + linkname = join(d, 'linkA') + path = self.ASubclass(linkname) + self.validate_object(path, linkname) + os.symlink(filename, linkname) + try: + self.validate_object(path.readlink(), filename) + finally: + os.unlink(linkname) def test_resolve(self): # create a file in a temp directory From 4be7b7a104503345f7c37f83d1929d5ca54d3aa1 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Tue, 18 Aug 2020 12:36:11 -0700 Subject: [PATCH 14/20] bpo-41109: cleanup test_resolve --- Lib/test/test_pathlib.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 8f01277238c865..cc1ea0e6969c10 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2781,9 +2781,10 @@ def test_readlink(self): def test_resolve(self): # create a file in a temp directory d = BASE - filename = 'fileA' - self.validate_object(self.ASubclass(d, filename).resolve(strict=True), - join(d, filename)) + filename = join(d, 'fileA') + path = self.ASubclass(d, filename) + self.validate_object(path, filename) + self.validate_object(path.resolve(strict=True), filename) @only_posix From 1660fa69e223d616dbe29713f14a8ba449fbe44a Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Tue, 18 Aug 2020 12:38:31 -0700 Subject: [PATCH 15/20] bpo-41109: cleanup test_glob --- Lib/test/test_pathlib.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index cc1ea0e6969c10..0a24625e3b337a 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2732,13 +2732,13 @@ def test_glob(self): filename = 'fileA' path = self.ASubclass(d) self.validate_object(path, d) - cases = [(path.glob, 'f*', join(d, filename)), # _WildcardSelector - (path.glob, 'fileA', join(d, filename)), # _PreciseSelector - (path.glob, '**', d)] # _RecursiveWildcardSelector + cases = [('f*', join(d, filename)), # _WildcardSelector + ('fileA', join(d, filename)), # _PreciseSelector + ('**', d)] # _RecursiveWildcardSelector - for func, param, value in cases: + for param, value in cases: n = 0 - for name in func(param): + for name in path.glob(param): self.validate_object(name, value) n += 1 self.assertEqual(n, 1) From 2a270163e3267d45183aeb8f263c041de738bc17 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Tue, 18 Aug 2020 12:59:32 -0700 Subject: [PATCH 16/20] bpo-41109: test all initialization cases --- Lib/test/test_pathlib.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 0a24625e3b337a..4d33e59505c2d2 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -2612,7 +2612,10 @@ def validate_object(self, path, value): self.assertEqual(str(path), value) def test_class_initialization(self): - self.validate_object(self.ASubclass('a/b/c.foo'), 'a/b/c.foo') + self.validate_object(self.ASubclass('/a/b/c.foo'), '/a/b/c.foo') + self.validate_object(self.ASubclass('/', 'a', 'b', 'c.foo'), + '/a/b/c.foo') + self.validate_object(self.ASubclass(self.cls('/a/b')), '/a/b') def test_joinpath(self): self.validate_object(self.ASubclass('a/b/c.foo').parent.joinpath('d/e'), @@ -2673,7 +2676,11 @@ def validate_object(self, path, value): self.assertEqual(str(path), value) def test_class_initialization(self): - self.validate_object(self.ASubclass('c:\\a\\b\\c.foo'), 'c:\\a\\b\\c.foo') + self.validate_object(self.ASubclass('c:\\a\\b\\c.foo'), + 'c:\\a\\b\\c.foo') + self.validate_object(self.ASubclass('c:', '\\', 'a', 'b', 'c.foo'), + 'c:\\a\\b\\c.foo') + self.validate_object(self.ASubclass(self.cls('c:\\a')), 'c:\\a') def test_joinpath(self): self.validate_object(self.ASubclass('c:\\a\\b\\c.foo').parent.joinpath('d\\e'), @@ -2810,7 +2817,10 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def test_class_initialization(self): - self.validate_object(self.ASubclass('a/b/c.foo'), 'a/b/c.foo') + self.validate_object(self.ASubclass('/a/b/c.foo'), '/a/b/c.foo') + self.validate_object(self.ASubclass('/', 'a', 'b', 'c.foo'), + '/a/b/c.foo') + self.validate_object(self.ASubclass(self.cls('/a/b')), '/a/b') def test_absolute(self): relpath = 'a/b/c.foo' @@ -2851,7 +2861,11 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def test_class_initialization(self): - self.validate_object(self.ASubclass('c:\\a\\b\\c.foo'), 'c:\\a\\b\\c.foo') + self.validate_object(self.ASubclass('c:\\a\\b\\c.foo'), + 'c:\\a\\b\\c.foo') + self.validate_object(self.ASubclass('c:', '\\', 'a', 'b', 'c.foo'), + 'c:\\a\\b\\c.foo') + self.validate_object(self.ASubclass(self.cls('c:\\a')), 'c:\\a') def test_absolute(self): relpath = 'a\\b\\c.foo' From 4014a16c24c2de889c935aafcd0dd7df21d27f5e Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Tue, 18 Aug 2020 18:23:10 -0700 Subject: [PATCH 17/20] bpo-41109: news blurb --- .../next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst diff --git a/Misc/NEWS.d/next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst b/Misc/NEWS.d/next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst new file mode 100644 index 00000000000000..1d4cc9e3dca524 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst @@ -0,0 +1,4 @@ +Subclasses of :class:`Path`: and :class:`PurePath`: now call the +:func:`__new__`: and :func:`__init__`: functions of the subclasses when +instantiating new subclass objects returned by various :class:`Path`: and +:class:`PurePath`: functions and properties. Patch by Jeffrey Kintscher. From 31b449e21f178da06e1fc7f85464b5daba098be5 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Tue, 18 Aug 2020 18:36:51 -0700 Subject: [PATCH 18/20] bpo-41109: What's New entry and better news blurb --- Doc/whatsnew/3.10.rst | 8 ++++++++ .../next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index ec0343f2ce71e8..3ddeb26122e4e4 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -120,6 +120,14 @@ Added the *root_dir* and *dir_fd* parameters in :func:`~glob.glob` and :func:`~glob.iglob` which allow to specify the root directory for searching. (Contributed by Serhiy Storchaka in :issue:`38144`.) +pathlib +------- +Subclasses of :class:`Path` and :class:`PurePath` now call the +:func:`__new__` and :func:`__init__` functions of the subclasses when +instantiating new subclass objects returned by various :class:`Path` and +:class:`PurePath` functions and properties. (Contributed by Jeffrey Kintscher +in :issue:`41109`.) + py_compile ---------- diff --git a/Misc/NEWS.d/next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst b/Misc/NEWS.d/next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst index 1d4cc9e3dca524..52a230c319e05e 100644 --- a/Misc/NEWS.d/next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst +++ b/Misc/NEWS.d/next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst @@ -1,4 +1,4 @@ -Subclasses of :class:`Path`: and :class:`PurePath`: now call the -:func:`__new__`: and :func:`__init__`: functions of the subclasses when -instantiating new subclass objects returned by various :class:`Path`: and -:class:`PurePath`: functions and properties. Patch by Jeffrey Kintscher. +Subclasses of :class:`Path` and :class:`PurePath` now call the +:func:`__new__` and :func:`__init__` functions of the subclasses when +instantiating new subclass objects returned by various :class:`Path` and +:class:`PurePath` functions and properties. Patch by Jeffrey Kintscher. From 86bb25b1d029fcd83c3ea5d7560410ad1d2f3a7e Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Tue, 18 Aug 2020 20:02:37 -0700 Subject: [PATCH 19/20] bpo-41109: improve What's New and News entries --- Doc/whatsnew/3.10.rst | 8 ++++---- .../next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 3a87a7730a11e0..7c0459852eb883 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -134,11 +134,11 @@ Added :func:`os.cpu_count()` support for VxWorks RTOS. pathlib ------- -Subclasses of :class:`Path` and :class:`PurePath` now call the +Subclasses of :class:`pathlib.Path` and :class:`pathlib.PurePath` now call the :func:`__new__` and :func:`__init__` functions of the subclasses when -instantiating new subclass objects returned by various :class:`Path` and -:class:`PurePath` functions and properties. (Contributed by Jeffrey Kintscher -in :issue:`41109`.) +instantiating new subclass objects returned by various :class:`pathlib.Path` and +:class:`pathlib.PurePath` functions and properties. (Contributed by Jeffrey +Kintscher in :issue:`41109`.) py_compile ---------- diff --git a/Misc/NEWS.d/next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst b/Misc/NEWS.d/next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst index 52a230c319e05e..a9ac25510cbbf1 100644 --- a/Misc/NEWS.d/next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst +++ b/Misc/NEWS.d/next/Library/2020-08-18-18-22-56.bpo-41109.QcFpg2.rst @@ -1,4 +1,4 @@ -Subclasses of :class:`Path` and :class:`PurePath` now call the +Subclasses of :class:`pathlib.Path` and :class:`pathlib.PurePath` now call the :func:`__new__` and :func:`__init__` functions of the subclasses when -instantiating new subclass objects returned by various :class:`Path` and -:class:`PurePath` functions and properties. Patch by Jeffrey Kintscher. +instantiating new subclass objects returned by various :class:`pathlib.Path` and +:class:`pathlib.PurePath` functions and properties. Patch by Jeffrey Kintscher. From b2c62a6ce67744ac016ef93439864ebec9779281 Mon Sep 17 00:00:00 2001 From: Jeffrey Kintscher Date: Tue, 18 Aug 2020 21:45:16 -0700 Subject: [PATCH 20/20] bpo-41109: simplify _new_from_parts() and _new_from_parsed_parts() in PurePath --- Lib/pathlib.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 886c17b2ca4324..fb28532d76e709 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -720,23 +720,15 @@ def _from_parsed_parts(cls, drv, root, parts, init=True): self._init() return self - def _new_from_parts(self, args, init=True): - obj = type(self)() - drv, root, parts = obj._parse_args(args) - obj._drv = drv - obj._root = root - obj._parts = parts - if init: - obj._init() + def _new_from_parts(self, args): + obj = type(self)(*args) return obj - def _new_from_parsed_parts(self, drv, root, parts, init=True): + def _new_from_parsed_parts(self, drv, root, parts): obj = type(self)() obj._drv = drv obj._root = root obj._parts = parts - if init: - obj._init() return obj @classmethod @@ -1222,7 +1214,7 @@ def absolute(self): return self # FIXME this must defer to the specific flavour (and, under Windows, # use nt._getfullpathname()) - obj = self._new_from_parts([os.getcwd()] + self._parts, init=False) + obj = self._new_from_parts([os.getcwd()] + self._parts) obj._init(template=self) return obj @@ -1240,7 +1232,7 @@ def resolve(self, strict=False): s = str(self.absolute()) # Now we have no symlinks in the path, it's safe to normalize it. normed = self._flavour.pathmod.normpath(s) - obj = self._new_from_parts((normed,), init=False) + obj = self._new_from_parts((normed,)) obj._init(template=self) return obj @@ -1310,7 +1302,7 @@ def readlink(self): Return the path to which the symbolic link points. """ path = self._accessor.readlink(self) - obj = self._new_from_parts((path,), init=False) + obj = self._new_from_parts((path,)) obj._init(template=self) return obj