Skip to content

Commit cba36a3

Browse files
committed
pythonGH-106037: Allow subclasses of pathlib.PurePath to opt into os.PathLike
We made it possible to subclass `pathlib.PurePath` in a68e585, which landed in 3.12. However, user subclasses automatically inherit an `__fspath__()` method, which may not be appropriate. For example, a user subclass may implement a "virtual" filesystem to provide access to a `.zip` file or FTP server. But it would be highly surprising if `open(FTPPath(...))` attempted to open a *local* file. This patch makes the `os.PathLike` interface opt-in. In pathlib itself, we opt into the `os.PathLike` interface for `PurePosixPath`, `PureWindowsPath` and `Path`. As `PurePath` is not instantiable (you always get a `PurePosixPath` or `PureWindowsPath` object back), this is backwards compatible with 3.11, but not with earlier 3.12 betas.
1 parent 7b3ed5b commit cba36a3

File tree

3 files changed

+29
-9
lines changed

3 files changed

+29
-9
lines changed

Lib/pathlib.py

+17-9
Original file line numberDiff line numberDiff line change
@@ -399,9 +399,6 @@ def __str__(self):
399399
self._tail) or '.'
400400
return self._str
401401

402-
def __fspath__(self):
403-
return str(self)
404-
405402
def as_posix(self):
406403
"""Return the string representation of the path with forward (/)
407404
slashes."""
@@ -411,7 +408,7 @@ def as_posix(self):
411408
def __bytes__(self):
412409
"""Return the bytes representation of the path. This is only
413410
recommended to use under Unix."""
414-
return os.fsencode(self)
411+
return os.fsencode(str(self))
415412

416413
def __repr__(self):
417414
return "{}({!r})".format(self.__class__.__name__, self.as_posix())
@@ -743,11 +740,6 @@ def match(self, path_pattern, *, case_sensitive=None):
743740
raise ValueError("empty pattern")
744741

745742

746-
# Subclassing os.PathLike makes isinstance() checks slower,
747-
# which in turn makes Path construction slower. Register instead!
748-
os.PathLike.register(PurePath)
749-
750-
751743
class PurePosixPath(PurePath):
752744
"""PurePath subclass for non-Windows systems.
753745
@@ -757,6 +749,8 @@ class PurePosixPath(PurePath):
757749
_flavour = posixpath
758750
__slots__ = ()
759751

752+
def __fspath__(self):
753+
return str(self)
760754

761755
class PureWindowsPath(PurePath):
762756
"""PurePath subclass for Windows systems.
@@ -767,6 +761,13 @@ class PureWindowsPath(PurePath):
767761
_flavour = ntpath
768762
__slots__ = ()
769763

764+
def __fspath__(self):
765+
return str(self)
766+
767+
768+
# These classes implement os.PathLike for backwards compatibility.
769+
os.PathLike.register(PurePosixPath)
770+
os.PathLike.register(PureWindowsPath)
770771

771772
# Filesystem-accessing classes
772773

@@ -1174,6 +1175,9 @@ def __new__(cls, *args, **kwargs):
11741175
cls = WindowsPath if os.name == 'nt' else PosixPath
11751176
return object.__new__(cls)
11761177

1178+
def __fspath__(self):
1179+
return str(self)
1180+
11771181
@classmethod
11781182
def cwd(cls):
11791183
"""Return a new path pointing to the current working directory."""
@@ -1398,6 +1402,10 @@ def expanduser(self):
13981402

13991403
return self
14001404

1405+
# Subclassing os.PathLike makes isinstance() checks slower,
1406+
# which in turn makes Path construction slower. Register instead!
1407+
os.PathLike.register(Path)
1408+
14011409

14021410
class PosixPath(Path, PurePosixPath):
14031411
"""Path subclass for non-Windows systems.

Lib/test/test_pathlib.py

+9
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,15 @@ class cls(pathlib.PurePath):
15451545
# repr() roundtripping is not supported in custom subclass.
15461546
test_repr_roundtrips = None
15471547

1548+
# PurePath subclass is not automatically path-like
1549+
def test_fspath_common(self):
1550+
P = self.cls
1551+
p = P()
1552+
self.assertFalse(issubclass(P, os.PathLike))
1553+
self.assertFalse(isinstance(p, os.PathLike))
1554+
with self.assertRaises(TypeError):
1555+
os.fspath(p)
1556+
15481557

15491558
@only_posix
15501559
class PosixPathAsPureTest(PurePosixPathTest):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix misidentification of :class:`pathlib.PurePath` user subclasses as
2+
:class:`os.PathLike`. Subclasses are free to define an
3+
:meth:`~os.PathLike.__fspath__` method, but do not inherit one.

0 commit comments

Comments
 (0)