diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index ad801d5d7cdc4b..7b4c48bb67ec08 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -106,9 +106,9 @@ we also call *flavours*: PurePosixPath('setup.py') Each element of *pathsegments* can be either a string representing a - path segment, or an object implementing the :class:`os.PathLike` interface + path segment, an object implementing the :class:`os.PathLike` interface where the :meth:`~os.PathLike.__fspath__` method returns a string, - such as another path object:: + or another path object:: >>> PurePath('foo', 'some/path', 'bar') PurePosixPath('foo/some/path/bar') @@ -151,11 +151,6 @@ we also call *flavours*: to ``PurePosixPath('bar')``, which is wrong if ``foo`` is a symbolic link to another directory) - Pure path objects implement the :class:`os.PathLike` interface, allowing them - to be used anywhere the interface is accepted. - - .. versionchanged:: 3.6 - Added support for the :class:`os.PathLike` interface. .. class:: PurePosixPath(*pathsegments) @@ -232,14 +227,6 @@ relative path (e.g., ``r'\foo'``):: >>> PureWindowsPath('c:/Windows', '/Program Files') PureWindowsPath('c:/Program Files') -A path object can be used anywhere an object implementing :class:`os.PathLike` -is accepted:: - - >>> import os - >>> p = PurePath('/etc') - >>> os.fspath(p) - '/etc' - The string representation of a path is the raw filesystem path itself (in native form, e.g. with backslashes under Windows), which you can pass to any function taking a file path as a string:: @@ -251,16 +238,6 @@ pass to any function taking a file path as a string:: >>> str(p) 'c:\\Program Files' -Similarly, calling :class:`bytes` on a path gives the raw filesystem path as a -bytes object, as encoded by :func:`os.fsencode`:: - - >>> bytes(p) - b'/etc' - -.. note:: - Calling :class:`bytes` is only recommended under Unix. Under Windows, - the unicode form is the canonical representation of filesystem paths. - Accessing individual parts ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -781,6 +758,34 @@ bugs or failures in your application):: NotImplementedError: cannot instantiate 'WindowsPath' on your system +Operators +^^^^^^^^^ + +Concrete path objects implement the :class:`os.PathLike` interface, +allowing them to be used anywhere the interface is accepted:: + + >>> import os + >>> p = Path('/etc') + >>> os.fspath(p) + '/etc' + +.. versionchanged:: 3.6 + Added support for the :class:`os.PathLike` interface. + +Calling :class:`bytes` on a concrete path gives the raw filesystem path as a +bytes object, as encoded by :func:`os.fsencode`:: + + >>> bytes(p) + b'/etc' + +.. note:: + Calling :class:`bytes` is only recommended under Unix. Under Windows, + the unicode form is the canonical representation of filesystem paths. + +For backwards compatibility, these operations are also supported by +instances of :class:`PurePosixPath` and :class:`PureWindowsPath`. + + Methods ^^^^^^^ diff --git a/Lib/pathlib.py b/Lib/pathlib.py index d8c597f1027f30..f87fb29133c70e 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -391,20 +391,12 @@ def __str__(self): self._tail) or '.' return self._str - def __fspath__(self): - return str(self) - def as_posix(self): """Return the string representation of the path with forward (/) slashes.""" f = self._flavour return str(self).replace(f.sep, '/') - def __bytes__(self): - """Return the bytes representation of the path. This is only - recommended to use under Unix.""" - return os.fsencode(self) - def __repr__(self): return "{}({!r})".format(self.__class__.__name__, self.as_posix()) @@ -735,12 +727,25 @@ def match(self, path_pattern, *, case_sensitive=None): raise ValueError("empty pattern") +class _PurePathExt(PurePath): + """PurePath subclass that provides __fspath__ and __bytes__ methods.""" + __slots__ = () + + def __fspath__(self): + return str(self) + + def __bytes__(self): + """Return the bytes representation of the path. This is only + recommended to use under Unix.""" + return os.fsencode(self) + + # Subclassing os.PathLike makes isinstance() checks slower, # which in turn makes Path construction slower. Register instead! -os.PathLike.register(PurePath) +os.PathLike.register(_PurePathExt) -class PurePosixPath(PurePath): +class PurePosixPath(_PurePathExt): """PurePath subclass for non-Windows systems. On a POSIX system, instantiating a PurePath should return this object. @@ -750,7 +755,7 @@ class PurePosixPath(PurePath): __slots__ = () -class PureWindowsPath(PurePath): +class PureWindowsPath(_PurePathExt): """PurePath subclass for Windows systems. On a Windows system, instantiating a PurePath should return this object. @@ -763,7 +768,7 @@ class PureWindowsPath(PurePath): # Filesystem-accessing classes -class Path(PurePath): +class Path(_PurePathExt): """PurePath subclass that can make system calls. Path represents a filesystem path but unlike PurePath, also offers diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 02a0f25e0bd991..2512d1b4d88a9c 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -266,11 +266,6 @@ def test_as_posix_common(self): self.assertEqual(P(pathstr).as_posix(), pathstr) # Other tests for as_posix() are in test_equivalences(). - def test_as_bytes_common(self): - sep = os.fsencode(self.sep) - P = self.cls - self.assertEqual(bytes(P('a/b')), b'a' + sep + b'b') - def test_as_uri_common(self): P = self.cls with self.assertRaises(ValueError): @@ -419,12 +414,6 @@ def test_parts_common(self): parts = p.parts self.assertEqual(parts, (sep, 'a', 'b')) - def test_fspath_common(self): - P = self.cls - p = P('a/b') - self._check_str(p.__fspath__(), ('a/b',)) - self._check_str(os.fspath(p), ('a/b',)) - def test_equivalences(self): for k, tuples in self.equivalences.items(): canon = k.replace('/', self.sep) @@ -763,7 +752,22 @@ def test_pickling_common(self): self.assertEqual(str(pp), str(p)) -class PurePosixPathTest(PurePathTest): +class PurePathExtTest(PurePathTest): + cls = pathlib._PurePathExt + + def test_fspath_common(self): + P = self.cls + p = P('a/b') + self._check_str(p.__fspath__(), ('a/b',)) + self._check_str(os.fspath(p), ('a/b',)) + + def test_bytes_common(self): + sep = os.fsencode(self.sep) + P = self.cls + self.assertEqual(bytes(P('a/b')), b'a' + sep + b'b') + + +class PurePosixPathTest(PurePathExtTest): cls = pathlib.PurePosixPath def test_drive_root_parts(self): @@ -857,7 +861,7 @@ def test_parse_windows_path(self): self.assertEqual(p, pp) -class PureWindowsPathTest(PurePathTest): +class PureWindowsPathTest(PurePathExtTest): cls = pathlib.PureWindowsPath equivalences = PurePathTest.equivalences.copy() @@ -1539,6 +1543,17 @@ class cls(pathlib.PurePath): # repr() roundtripping is not supported in custom subclass. test_repr_roundtrips = None + def test_not_path_like(self): + p = self.cls() + self.assertNotIsInstance(p, os.PathLike) + with self.assertRaises(TypeError): + os.fspath(p) + + def test_not_bytes_like(self): + p = self.cls() + with self.assertRaises(TypeError): + bytes(p) + @only_posix class PosixPathAsPureTest(PurePosixPathTest):