GH-125413: Revert addition of pathlib.Path.scandir() method (#127377)

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>
This commit is contained in:
Barney Gale 2024-12-05 21:39:43 +00:00 committed by GitHub
parent f4f530804b
commit 8b3cccf3f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 22 additions and 85 deletions

View file

@ -1289,35 +1289,6 @@ Reading directories
raised. 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) .. method:: Path.glob(pattern, *, case_sensitive=None, recurse_symlinks=False)
Glob the given relative *pattern* in the directory represented by this path, Glob the given relative *pattern* in the directory represented by this path,

View file

@ -532,12 +532,6 @@ pathlib
(Contributed by Barney Gale in :gh:`73991`.) (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 pdb
--- ---

View file

@ -94,7 +94,7 @@ class PathGlobber(_GlobberBase):
lexists = operator.methodcaller('exists', follow_symlinks=False) lexists = operator.methodcaller('exists', follow_symlinks=False)
add_slash = operator.methodcaller('joinpath', '') add_slash = operator.methodcaller('joinpath', '')
scandir = operator.methodcaller('scandir') scandir = operator.methodcaller('_scandir')
@staticmethod @staticmethod
def concat_path(path, text): 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: with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f:
return f.write(data) return f.write(data)
def scandir(self): def _scandir(self):
"""Yield os.DirEntry objects of the directory contents. """Yield os.DirEntry-like objects of the directory contents.
The children are yielded in arbitrary order, and the The children are yielded in arbitrary order, and the
special entries '.' and '..' are not included. special entries '.' and '..' are not included.
""" """
raise UnsupportedOperation(self._unsupported_msg('scandir()')) import contextlib
return contextlib.nullcontext(self.iterdir())
def iterdir(self): def iterdir(self):
"""Yield path objects of the directory contents. """Yield path objects of the directory contents.
@ -646,9 +647,7 @@ class PathBase(PurePathBase):
The children are yielded in arbitrary order, and the The children are yielded in arbitrary order, and the
special entries '.' and '..' are not included. special entries '.' and '..' are not included.
""" """
with self.scandir() as entries: raise UnsupportedOperation(self._unsupported_msg('iterdir()'))
names = [entry.name for entry in entries]
return map(self.joinpath, names)
def _glob_selector(self, parts, case_sensitive, recurse_symlinks): def _glob_selector(self, parts, case_sensitive, recurse_symlinks):
if case_sensitive is None: if case_sensitive is None:
@ -698,7 +697,7 @@ class PathBase(PurePathBase):
if not top_down: if not top_down:
paths.append((path, dirnames, filenames)) paths.append((path, dirnames, filenames))
try: try:
with path.scandir() as entries: with path._scandir() as entries:
for entry in entries: for entry in entries:
name = entry.name name = entry.name
try: try:

View file

@ -634,8 +634,8 @@ class Path(PathBase, PurePath):
path_str = path_str[:-1] path_str = path_str[:-1]
yield path_str yield path_str
def scandir(self): def _scandir(self):
"""Yield os.DirEntry objects of the directory contents. """Yield os.DirEntry-like objects of the directory contents.
The children are yielded in arbitrary order, and the The children are yielded in arbitrary order, and the
special entries '.' and '..' are not included. special entries '.' and '..' are not included.

View file

@ -1,5 +1,4 @@
import collections import collections
import contextlib
import io import io
import os import os
import errno 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') '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): class DummyPath(PathBase):
""" """
Simple implementation of PathBase that keeps files and directories in 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) stream = io.TextIOWrapper(stream, encoding=encoding, errors=errors, newline=newline)
return stream return stream
@contextlib.contextmanager def iterdir(self):
def scandir(self): path = str(self.resolve())
path = self.resolve() if path in self._files:
path_str = str(path) raise NotADirectoryError(errno.ENOTDIR, "Not a directory", path)
if path_str in self._files: elif path in self._directories:
raise NotADirectoryError(errno.ENOTDIR, "Not a directory", path_str) return iter([self / name for name in self._directories[path]])
elif path_str in self._directories:
yield iter([path.joinpath(name)._dir_entry for name in self._directories[path_str]])
else: else:
raise FileNotFoundError(errno.ENOENT, "File not found", path_str) raise FileNotFoundError(errno.ENOENT, "File not found", path)
@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)
def mkdir(self, mode=0o777, parents=False, exist_ok=False): def mkdir(self, mode=0o777, parents=False, exist_ok=False):
path = str(self.parent.resolve() / self.name) path = str(self.parent.resolve() / self.name)
@ -2214,9 +2184,9 @@ class DummyPathTest(DummyPurePathTest):
def test_scandir(self): def test_scandir(self):
p = self.cls(self.base) p = self.cls(self.base)
with p.scandir() as entries: with p._scandir() as entries:
self.assertTrue(list(entries)) self.assertTrue(list(entries))
with p.scandir() as entries: with p._scandir() as entries:
for entry in entries: for entry in entries:
child = p / entry.name child = p / entry.name
self.assertIsNotNone(entry) self.assertIsNotNone(entry)

View file

@ -597,7 +597,7 @@ TypeError is now raised instead of ValueError for some logical errors.
.. nonce: Jat5kq .. nonce: Jat5kq
.. section: Library .. 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 children and their file attributes. This is a trivial wrapper of
:func:`os.scandir`. :func:`os.scandir`.

View file

@ -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`.