]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-125413: Revert addition of `pathlib.Path.scandir()` method (#127377)
authorBarney Gale <barney.gale@gmail.com>
Thu, 5 Dec 2024 21:39:43 +0000 (21:39 +0000)
committerGitHub <noreply@github.com>
Thu, 5 Dec 2024 21:39:43 +0000 (21:39 +0000)
Remove documentation for `pathlib.Path.scandir()`, and rename the method to
`_scandir()`. In the private pathlib ABCs, make `iterdir()` abstract and
call it from `_scandir()`.

It's not worthwhile to add this method at the moment - see discussion:
https://discuss.python.org/t/ergonomics-of-new-pathlib-path-scandir/71721

Co-authored-by: Steve Dower <steve.dower@microsoft.com>
Doc/library/pathlib.rst
Doc/whatsnew/3.14.rst
Lib/pathlib/_abc.py
Lib/pathlib/_local.py
Lib/test/test_pathlib/test_pathlib_abc.py
Misc/NEWS.d/3.14.0a2.rst
Misc/NEWS.d/next/Library/2024-11-29-00-15-59.gh-issue-125413.WCN0vv.rst [new file with mode: 0644]

index a42ac1f8bcdf717d7428a3ea4f67353d65f14c76..4b48880d6d9a18d66937b5aff9fa7c630a092015 100644 (file)
@@ -1289,35 +1289,6 @@ Reading directories
    raised.
 
 
-.. method:: Path.scandir()
-
-   When the path points to a directory, return an iterator of
-   :class:`os.DirEntry` objects corresponding to entries in the directory. The
-   returned iterator supports the :term:`context manager` protocol. It is
-   implemented using :func:`os.scandir` and gives the same guarantees.
-
-   Using :meth:`~Path.scandir` instead of :meth:`~Path.iterdir` can
-   significantly increase the performance of code that also needs file type or
-   file attribute information, because :class:`os.DirEntry` objects expose
-   this information if the operating system provides it when scanning a
-   directory.
-
-   The following example displays the names of subdirectories. The
-   ``entry.is_dir()`` check will generally not make an additional system call::
-
-      >>> p = Path('docs')
-      >>> with p.scandir() as entries:
-      ...     for entry in entries:
-      ...         if entry.is_dir():
-      ...             entry.name
-      ...
-      '_templates'
-      '_build'
-      '_static'
-
-   .. versionadded:: 3.14
-
-
 .. method:: Path.glob(pattern, *, case_sensitive=None, recurse_symlinks=False)
 
    Glob the given relative *pattern* in the directory represented by this path,
index db25c037e509b67d394fcd77050305fefbe8e1e5..b300e34867943872340ff4dd6b95bf121530d13a 100644 (file)
@@ -532,12 +532,6 @@ pathlib
 
   (Contributed by Barney Gale in :gh:`73991`.)
 
-* Add :meth:`pathlib.Path.scandir` to scan a directory and return an iterator
-  of :class:`os.DirEntry` objects. This is exactly equivalent to calling
-  :func:`os.scandir` on a path object.
-
-  (Contributed by Barney Gale in :gh:`125413`.)
-
 
 pdb
 ---
index 2b314b6c9a16bf4746e8c592d7acb2201a29d87d..86617ff2616f33f9f83b0cf169fb09cad4c73dff 100644 (file)
@@ -94,7 +94,7 @@ class PathGlobber(_GlobberBase):
 
     lexists = operator.methodcaller('exists', follow_symlinks=False)
     add_slash = operator.methodcaller('joinpath', '')
-    scandir = operator.methodcaller('scandir')
+    scandir = operator.methodcaller('_scandir')
 
     @staticmethod
     def concat_path(path, text):
@@ -632,13 +632,14 @@ class PathBase(PurePathBase):
         with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f:
             return f.write(data)
 
-    def scandir(self):
-        """Yield os.DirEntry objects of the directory contents.
+    def _scandir(self):
+        """Yield os.DirEntry-like objects of the directory contents.
 
         The children are yielded in arbitrary order, and the
         special entries '.' and '..' are not included.
         """
-        raise UnsupportedOperation(self._unsupported_msg('scandir()'))
+        import contextlib
+        return contextlib.nullcontext(self.iterdir())
 
     def iterdir(self):
         """Yield path objects of the directory contents.
@@ -646,9 +647,7 @@ class PathBase(PurePathBase):
         The children are yielded in arbitrary order, and the
         special entries '.' and '..' are not included.
         """
-        with self.scandir() as entries:
-            names = [entry.name for entry in entries]
-        return map(self.joinpath, names)
+        raise UnsupportedOperation(self._unsupported_msg('iterdir()'))
 
     def _glob_selector(self, parts, case_sensitive, recurse_symlinks):
         if case_sensitive is None:
@@ -698,7 +697,7 @@ class PathBase(PurePathBase):
             if not top_down:
                 paths.append((path, dirnames, filenames))
             try:
-                with path.scandir() as entries:
+                with path._scandir() as entries:
                     for entry in entries:
                         name = entry.name
                         try:
index b5d9dc49f5846339225dccc4b4d40ce2c7c8680b..bb8a252c0e94e24e064e81a3ca49d9b80ed9da30 100644 (file)
@@ -634,8 +634,8 @@ class Path(PathBase, PurePath):
                 path_str = path_str[:-1]
             yield path_str
 
-    def scandir(self):
-        """Yield os.DirEntry objects of the directory contents.
+    def _scandir(self):
+        """Yield os.DirEntry-like objects of the directory contents.
 
         The children are yielded in arbitrary order, and the
         special entries '.' and '..' are not included.
index 5fa2f550cefcf4b714dad6069ac14f798329639c..7ba3fa823a30b974ec79e5d37d913e25471c010a 100644 (file)
@@ -1,5 +1,4 @@
 import collections
-import contextlib
 import io
 import os
 import errno
@@ -1418,24 +1417,6 @@ DummyPathStatResult = collections.namedtuple(
     'st_mode st_ino st_dev st_nlink st_uid st_gid st_size st_atime st_mtime st_ctime')
 
 
-class DummyDirEntry:
-    """
-    Minimal os.DirEntry-like object. Returned from DummyPath.scandir().
-    """
-    __slots__ = ('name', '_is_symlink', '_is_dir')
-
-    def __init__(self, name, is_symlink, is_dir):
-        self.name = name
-        self._is_symlink = is_symlink
-        self._is_dir = is_dir
-
-    def is_symlink(self):
-        return self._is_symlink
-
-    def is_dir(self, *, follow_symlinks=True):
-        return self._is_dir and (follow_symlinks or not self._is_symlink)
-
-
 class DummyPath(PathBase):
     """
     Simple implementation of PathBase that keeps files and directories in
@@ -1503,25 +1484,14 @@ class DummyPath(PathBase):
             stream = io.TextIOWrapper(stream, encoding=encoding, errors=errors, newline=newline)
         return stream
 
-    @contextlib.contextmanager
-    def scandir(self):
-        path = self.resolve()
-        path_str = str(path)
-        if path_str in self._files:
-            raise NotADirectoryError(errno.ENOTDIR, "Not a directory", path_str)
-        elif path_str in self._directories:
-            yield iter([path.joinpath(name)._dir_entry for name in self._directories[path_str]])
+    def iterdir(self):
+        path = str(self.resolve())
+        if path in self._files:
+            raise NotADirectoryError(errno.ENOTDIR, "Not a directory", path)
+        elif path in self._directories:
+            return iter([self / name for name in self._directories[path]])
         else:
-            raise FileNotFoundError(errno.ENOENT, "File not found", path_str)
-
-    @property
-    def _dir_entry(self):
-        path_str = str(self)
-        is_symlink = path_str in self._symlinks
-        is_directory = (path_str in self._directories
-                        if not is_symlink
-                        else self._symlinks[path_str][1])
-        return DummyDirEntry(self.name, is_symlink, is_directory)
+            raise FileNotFoundError(errno.ENOENT, "File not found", path)
 
     def mkdir(self, mode=0o777, parents=False, exist_ok=False):
         path = str(self.parent.resolve() / self.name)
@@ -2214,9 +2184,9 @@ class DummyPathTest(DummyPurePathTest):
 
     def test_scandir(self):
         p = self.cls(self.base)
-        with p.scandir() as entries:
+        with p._scandir() as entries:
             self.assertTrue(list(entries))
-        with p.scandir() as entries:
+        with p._scandir() as entries:
             for entry in entries:
                 child = p / entry.name
                 self.assertIsNotNone(entry)
index 7384ce54cb891422eeb9749e03d5134a7b680a26..d82ec98b7a3c8798ef08ea5d0d3f4c4c67aae5e4 100644 (file)
@@ -597,7 +597,7 @@ TypeError is now raised instead of ValueError for some logical errors.
 .. nonce: Jat5kq
 .. section: Library
 
-Add :meth:`pathlib.Path.scandir` method to efficiently fetch directory
+Add :meth:`!pathlib.Path.scandir` method to efficiently fetch directory
 children and their file attributes. This is a trivial wrapper of
 :func:`os.scandir`.
 
diff --git a/Misc/NEWS.d/next/Library/2024-11-29-00-15-59.gh-issue-125413.WCN0vv.rst b/Misc/NEWS.d/next/Library/2024-11-29-00-15-59.gh-issue-125413.WCN0vv.rst
new file mode 100644 (file)
index 0000000..b56a77b
--- /dev/null
@@ -0,0 +1,3 @@
+Revert addition of :meth:`!pathlib.Path.scandir`. This method was added in
+3.14.0a2. The optimizations remain for file system paths, but other
+subclasses should only have to implement :meth:`pathlib.Path.iterdir`.