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

bpo-41109: subclasses of pathlib.Path and pathlib.PurePath now call the subclass's __init__() and __new__() functions when returning new objects #21920

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
081cbc2
bpo-41109: separate object creation by Path and PurePath member varia…
websurfer5 Aug 16, 2020
0e4d53e
bpo-41109: Windows PurePath tests
websurfer5 Aug 17, 2020
53643f4
bpo-41109: use correct class in PureWindowsSubclassNewAndInitTest
websurfer5 Aug 17, 2020
c418958
bpo-41109: fix comment
websurfer5 Aug 17, 2020
34683c4
bpo-41109: fix class names and comments
websurfer5 Aug 17, 2020
19e0a40
bpo-41109: WindowsPath test suite
websurfer5 Aug 17, 2020
1853c6d
bpo-41109: add __init__() to Path and PurePath
websurfer5 Aug 17, 2020
17c141c
bpo-41109: remove extraneous **kwargs parameter from Path.__init__()
websurfer5 Aug 17, 2020
505cab0
bpo-41109: remove extraneous whitespace
websurfer5 Aug 17, 2020
c47bf06
bpo-41109: remove double-initialization in Path.__new__()
websurfer5 Aug 18, 2020
6607e77
bpo-41109: no longer need to call _init() from PurePath.__init__()
websurfer5 Aug 18, 2020
cf6afb0
bpo-41109: reorganize tests using a common base class for common tests
websurfer5 Aug 18, 2020
d25fdd7
bpo-41109: fix readlink test for Windows
websurfer5 Aug 18, 2020
4be7b7a
bpo-41109: cleanup test_resolve
websurfer5 Aug 18, 2020
1660fa6
bpo-41109: cleanup test_glob
websurfer5 Aug 18, 2020
2a27016
bpo-41109: test all initialization cases
websurfer5 Aug 18, 2020
4014a16
bpo-41109: news blurb
websurfer5 Aug 19, 2020
31b449e
bpo-41109: What's New entry and better news blurb
websurfer5 Aug 19, 2020
ebc8371
bpo-4119: Merge branch 'master' of github.com:python/cpython into fix…
websurfer5 Aug 19, 2020
86bb25b
bpo-41109: improve What's New and News entries
websurfer5 Aug 19, 2020
b2c62a6
bpo-41109: simplify _new_from_parts() and _new_from_parsed_parts() in…
websurfer5 Aug 19, 2020
6f363b9
Merge branch 'master' of github.com:python/cpython into fix-issue-41109
websurfer5 Aug 23, 2020
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
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ os
Added :func:`os.cpu_count()` support for VxWorks RTOS.
(Contributed by Peixing Xin in :issue:`41440`.)

pathlib
-------
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:`pathlib.Path` and
:class:`pathlib.PurePath` functions and properties. (Contributed by Jeffrey
Kintscher in :issue:`41109`.)

py_compile
----------

Expand Down
60 changes: 43 additions & 17 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -658,7 +658,13 @@ 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):
drv, root, parts = self._parse_args(args)
self._drv = drv
self._root = root
self._parts = parts

def __reduce__(self):
# Using the parts tuple helps share interned path parts
Expand Down Expand Up @@ -687,6 +693,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)
Expand All @@ -700,6 +709,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
Expand All @@ -708,6 +720,17 @@ def _from_parsed_parts(cls, drv, root, parts, init=True):
self._init()
return self

def _new_from_parts(self, args):
obj = type(self)(*args)
return obj

def _new_from_parsed_parts(self, drv, root, parts):
obj = type(self)()
obj._drv = drv
obj._root = root
obj._parts = parts
return obj

@classmethod
def _format_parsed_parts(cls, drv, root, parts):
if drv or root:
Expand All @@ -723,7 +746,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
Expand Down Expand Up @@ -867,7 +890,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):
Expand All @@ -892,8 +915,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
Expand Down Expand Up @@ -925,8 +948,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.
Expand Down Expand Up @@ -965,7 +988,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

Expand All @@ -977,7 +1000,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):
Expand Down Expand Up @@ -1065,13 +1088,16 @@ 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"
% (cls.__name__,))
self._init()
return self

def __init__(self, *args):
super().__init__(*args)
self._init()

def _init(self,
# Private non-constructor arguments
template=None,
Expand All @@ -1085,7 +1111,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
Expand Down Expand Up @@ -1188,7 +1214,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)
obj._init(template=self)
return obj

Expand All @@ -1206,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._from_parts((normed,), init=False)
obj = self._new_from_parts((normed,))
obj._init(template=self)
return obj

Expand Down Expand Up @@ -1276,7 +1302,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,))
obj._init(template=self)
return obj

Expand Down Expand Up @@ -1541,7 +1567,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

Expand Down
Loading