Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f86d754

Browse filesBrowse files
barneygalesrinivasreddy
authored andcommitted
pythonGH-127807: pathlib ABCs: remove PurePathBase._raw_paths (python#127883)
Remove the `PurePathBase` initializer, and make `with_segments()` and `__str__()` abstract. This allows us to drop the `_raw_paths` attribute, and also the `Parser.join()` protocol method.
1 parent ff26364 commit f86d754
Copy full SHA for f86d754

File tree

Expand file treeCollapse file tree

5 files changed

+92
-96
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+92
-96
lines changed

‎Lib/pathlib/_abc.py

Copy file name to clipboardExpand all lines: Lib/pathlib/_abc.py
+8-32Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -44,49 +44,25 @@ class PurePathBase:
4444
"""Base class for pure path objects.
4545
4646
This class *does not* provide several magic methods that are defined in
47-
its subclass PurePath. They are: __fspath__, __bytes__, __reduce__,
48-
__hash__, __eq__, __lt__, __le__, __gt__, __ge__. Its initializer and path
49-
joining methods accept only strings, not os.PathLike objects more broadly.
47+
its subclass PurePath. They are: __init__, __fspath__, __bytes__,
48+
__reduce__, __hash__, __eq__, __lt__, __le__, __gt__, __ge__.
5049
"""
5150

52-
__slots__ = (
53-
# The `_raw_paths` slot stores unjoined string paths. This is set in
54-
# the `__init__()` method.
55-
'_raw_paths',
56-
)
51+
__slots__ = ()
5752
parser = posixpath
5853
_globber = PathGlobber
5954

60-
def __init__(self, *args):
61-
for arg in args:
62-
if not isinstance(arg, str):
63-
raise TypeError(
64-
f"argument should be a str, not {type(arg).__name__!r}")
65-
self._raw_paths = list(args)
66-
6755
def with_segments(self, *pathsegments):
6856
"""Construct a new path object from any number of path-like objects.
6957
Subclasses may override this method to customize how new path objects
7058
are created from methods like `iterdir()`.
7159
"""
72-
return type(self)(*pathsegments)
60+
raise NotImplementedError
7361

7462
def __str__(self):
7563
"""Return the string representation of the path, suitable for
7664
passing to system calls."""
77-
paths = self._raw_paths
78-
if len(paths) == 1:
79-
return paths[0]
80-
elif paths:
81-
# Join path segments from the initializer.
82-
path = self.parser.join(*paths)
83-
# Cache the joined path.
84-
paths.clear()
85-
paths.append(path)
86-
return path
87-
else:
88-
paths.append('')
89-
return ''
65+
raise NotImplementedError
9066

9167
def as_posix(self):
9268
"""Return the string representation of the path with forward (/)
@@ -234,17 +210,17 @@ def joinpath(self, *pathsegments):
234210
paths) or a totally different path (if one of the arguments is
235211
anchored).
236212
"""
237-
return self.with_segments(*self._raw_paths, *pathsegments)
213+
return self.with_segments(str(self), *pathsegments)
238214

239215
def __truediv__(self, key):
240216
try:
241-
return self.with_segments(*self._raw_paths, key)
217+
return self.with_segments(str(self), key)
242218
except TypeError:
243219
return NotImplemented
244220

245221
def __rtruediv__(self, key):
246222
try:
247-
return self.with_segments(key, *self._raw_paths)
223+
return self.with_segments(key, str(self))
248224
except TypeError:
249225
return NotImplemented
250226

‎Lib/pathlib/_local.py

Copy file name to clipboardExpand all lines: Lib/pathlib/_local.py
+30-7Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ class PurePath(PurePathBase):
7777
"""
7878

7979
__slots__ = (
80+
# The `_raw_paths` slot stores unjoined string paths. This is set in
81+
# the `__init__()` method.
82+
'_raw_paths',
83+
8084
# The `_drv`, `_root` and `_tail_cached` slots store parsed and
8185
# normalized parts of the path. They are set when any of the `drive`,
8286
# `root` or `_tail` properties are accessed for the first time. The
@@ -140,9 +144,15 @@ def __init__(self, *args):
140144
"object where __fspath__ returns a str, "
141145
f"not {type(path).__name__!r}")
142146
paths.append(path)
143-
# Avoid calling super().__init__, as an optimisation
144147
self._raw_paths = paths
145148

149+
def with_segments(self, *pathsegments):
150+
"""Construct a new path object from any number of path-like objects.
151+
Subclasses may override this method to customize how new path objects
152+
are created from methods like `iterdir()`.
153+
"""
154+
return type(self)(*pathsegments)
155+
146156
def joinpath(self, *pathsegments):
147157
"""Combine this path with one or several arguments, and return a
148158
new path representing either a subpath (if all arguments are relative
@@ -304,14 +314,29 @@ def _parse_pattern(cls, pattern):
304314
parts.append('')
305315
return parts
306316

317+
@property
318+
def _raw_path(self):
319+
paths = self._raw_paths
320+
if len(paths) == 1:
321+
return paths[0]
322+
elif paths:
323+
# Join path segments from the initializer.
324+
path = self.parser.join(*paths)
325+
# Cache the joined path.
326+
paths.clear()
327+
paths.append(path)
328+
return path
329+
else:
330+
paths.append('')
331+
return ''
332+
307333
@property
308334
def drive(self):
309335
"""The drive prefix (letter or UNC path), if any."""
310336
try:
311337
return self._drv
312338
except AttributeError:
313-
raw_path = PurePathBase.__str__(self)
314-
self._drv, self._root, self._tail_cached = self._parse_path(raw_path)
339+
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
315340
return self._drv
316341

317342
@property
@@ -320,17 +345,15 @@ def root(self):
320345
try:
321346
return self._root
322347
except AttributeError:
323-
raw_path = PurePathBase.__str__(self)
324-
self._drv, self._root, self._tail_cached = self._parse_path(raw_path)
348+
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
325349
return self._root
326350

327351
@property
328352
def _tail(self):
329353
try:
330354
return self._tail_cached
331355
except AttributeError:
332-
raw_path = PurePathBase.__str__(self)
333-
self._drv, self._root, self._tail_cached = self._parse_path(raw_path)
356+
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
334357
return self._tail_cached
335358

336359
@property

‎Lib/pathlib/_types.py

Copy file name to clipboardExpand all lines: Lib/pathlib/_types.py
-1Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ class Parser(Protocol):
1414
"""
1515

1616
sep: str
17-
def join(self, path: str, *paths: str) -> str: ...
1817
def split(self, path: str) -> tuple[str, str]: ...
1918
def splitdrive(self, path: str) -> tuple[str, str]: ...
2019
def splitext(self, path: str) -> tuple[str, str]: ...

‎Lib/test/test_pathlib/test_pathlib.py

Copy file name to clipboardExpand all lines: Lib/test/test_pathlib/test_pathlib.py
+25Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,31 @@ def test_fspath_common(self):
229229
self._check_str(p.__fspath__(), ('a/b',))
230230
self._check_str(os.fspath(p), ('a/b',))
231231

232+
def test_bytes(self):
233+
P = self.cls
234+
with self.assertRaises(TypeError):
235+
P(b'a')
236+
with self.assertRaises(TypeError):
237+
P(b'a', 'b')
238+
with self.assertRaises(TypeError):
239+
P('a', b'b')
240+
with self.assertRaises(TypeError):
241+
P('a').joinpath(b'b')
242+
with self.assertRaises(TypeError):
243+
P('a') / b'b'
244+
with self.assertRaises(TypeError):
245+
b'a' / P('b')
246+
with self.assertRaises(TypeError):
247+
P('a').match(b'b')
248+
with self.assertRaises(TypeError):
249+
P('a').relative_to(b'b')
250+
with self.assertRaises(TypeError):
251+
P('a').with_name(b'b')
252+
with self.assertRaises(TypeError):
253+
P('a').with_stem(b'b')
254+
with self.assertRaises(TypeError):
255+
P('a').with_suffix(b'b')
256+
232257
def test_bytes_exc_message(self):
233258
P = self.cls
234259
message = (r"argument should be a str or an os\.PathLike object "

‎Lib/test/test_pathlib/test_pathlib_abc.py

Copy file name to clipboardExpand all lines: Lib/test/test_pathlib/test_pathlib_abc.py
+29-56Lines changed: 29 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,15 @@ def test_parser(self):
5353

5454

5555
class DummyPurePath(PurePathBase):
56-
__slots__ = ()
56+
__slots__ = ('_segments',)
57+
58+
def __init__(self, *segments):
59+
self._segments = segments
60+
61+
def __str__(self):
62+
if self._segments:
63+
return self.parser.join(*self._segments)
64+
return ''
5765

5866
def __eq__(self, other):
5967
if not isinstance(other, DummyPurePath):
@@ -66,6 +74,9 @@ def __hash__(self):
6674
def __repr__(self):
6775
return "{}({!r})".format(self.__class__.__name__, self.as_posix())
6876

77+
def with_segments(self, *pathsegments):
78+
return type(self)(*pathsegments)
79+
6980

7081
class DummyPurePathTest(unittest.TestCase):
7182
cls = DummyPurePath
@@ -97,30 +108,11 @@ def test_constructor_common(self):
97108
P('a/b/c')
98109
P('/a/b/c')
99110

100-
def test_bytes(self):
101-
P = self.cls
102-
with self.assertRaises(TypeError):
103-
P(b'a')
104-
with self.assertRaises(TypeError):
105-
P(b'a', 'b')
106-
with self.assertRaises(TypeError):
107-
P('a', b'b')
108-
with self.assertRaises(TypeError):
109-
P('a').joinpath(b'b')
110-
with self.assertRaises(TypeError):
111-
P('a') / b'b'
112-
with self.assertRaises(TypeError):
113-
b'a' / P('b')
114-
with self.assertRaises(TypeError):
115-
P('a').match(b'b')
116-
with self.assertRaises(TypeError):
117-
P('a').relative_to(b'b')
118-
with self.assertRaises(TypeError):
119-
P('a').with_name(b'b')
120-
with self.assertRaises(TypeError):
121-
P('a').with_stem(b'b')
122-
with self.assertRaises(TypeError):
123-
P('a').with_suffix(b'b')
111+
def test_fspath_common(self):
112+
self.assertRaises(TypeError, os.fspath, self.cls(''))
113+
114+
def test_as_bytes_common(self):
115+
self.assertRaises(TypeError, bytes, self.cls(''))
124116

125117
def _check_str_subclass(self, *args):
126118
# Issue #21127: it should be possible to construct a PurePath object
@@ -1286,36 +1278,6 @@ def test_is_absolute_windows(self):
12861278
# Tests for the virtual classes.
12871279
#
12881280

1289-
class PathBaseTest(PurePathBaseTest):
1290-
cls = PathBase
1291-
1292-
def test_not_implemented_error(self):
1293-
p = self.cls('')
1294-
e = NotImplementedError
1295-
self.assertRaises(e, p.stat)
1296-
self.assertRaises(e, p.exists)
1297-
self.assertRaises(e, p.is_dir)
1298-
self.assertRaises(e, p.is_file)
1299-
self.assertRaises(e, p.is_symlink)
1300-
self.assertRaises(e, p.open)
1301-
self.assertRaises(e, p.read_bytes)
1302-
self.assertRaises(e, p.read_text)
1303-
self.assertRaises(e, p.write_bytes, b'foo')
1304-
self.assertRaises(e, p.write_text, 'foo')
1305-
self.assertRaises(e, p.iterdir)
1306-
self.assertRaises(e, lambda: list(p.glob('*')))
1307-
self.assertRaises(e, lambda: list(p.rglob('*')))
1308-
self.assertRaises(e, lambda: list(p.walk()))
1309-
self.assertRaises(e, p.readlink)
1310-
self.assertRaises(e, p.symlink_to, 'foo')
1311-
self.assertRaises(e, p.mkdir)
1312-
1313-
def test_fspath_common(self):
1314-
self.assertRaises(TypeError, os.fspath, self.cls(''))
1315-
1316-
def test_as_bytes_common(self):
1317-
self.assertRaises(TypeError, bytes, self.cls(''))
1318-
13191281

13201282
class DummyPathIO(io.BytesIO):
13211283
"""
@@ -1342,11 +1304,19 @@ class DummyPath(PathBase):
13421304
Simple implementation of PathBase that keeps files and directories in
13431305
memory.
13441306
"""
1345-
__slots__ = ()
1307+
__slots__ = ('_segments')
13461308

13471309
_files = {}
13481310
_directories = {}
13491311

1312+
def __init__(self, *segments):
1313+
self._segments = segments
1314+
1315+
def __str__(self):
1316+
if self._segments:
1317+
return self.parser.join(*self._segments)
1318+
return ''
1319+
13501320
def __eq__(self, other):
13511321
if not isinstance(other, DummyPath):
13521322
return NotImplemented
@@ -1358,6 +1328,9 @@ def __hash__(self):
13581328
def __repr__(self):
13591329
return "{}({!r})".format(self.__class__.__name__, self.as_posix())
13601330

1331+
def with_segments(self, *pathsegments):
1332+
return type(self)(*pathsegments)
1333+
13611334
def stat(self, *, follow_symlinks=True):
13621335
path = str(self).rstrip('/')
13631336
if path in self._files:

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.