Skip to content

Commit

Permalink
pythonGH-125413: Revert addition of pathlib.Path.scandir() method
Browse files Browse the repository at this point in the history
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
  • Loading branch information
barneygale committed Nov 29, 2024
1 parent b83be9c commit f51afae
Show file tree
Hide file tree
Showing 6 changed files with 18 additions and 82 deletions.
29 changes: 0 additions & 29 deletions Doc/library/pathlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 0 additions & 6 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -509,12 +509,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
---
Expand Down
13 changes: 6 additions & 7 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -640,23 +640,22 @@ def write_text(self, data, encoding=None, errors=None, newline=None):
with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f:
return f.write(data)

def scandir(self):
def _scandir(self):
"""Yield os.DirEntry 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.
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:
Expand Down Expand Up @@ -706,7 +705,7 @@ def walk(self, top_down=True, on_error=None, follow_symlinks=False):
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:
Expand Down
2 changes: 1 addition & 1 deletion Lib/pathlib/_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ def _filter_trailing_slash(self, paths):
path_str = path_str[:-1]
yield path_str

def scandir(self):
def _scandir(self):
"""Yield os.DirEntry objects of the directory contents.
The children are yielded in arbitrary order, and the
Expand Down
48 changes: 9 additions & 39 deletions Lib/test/test_pathlib/test_pathlib_abc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import collections
import contextlib
import io
import os
import errno
Expand Down Expand Up @@ -1421,24 +1420,6 @@ def close(self):
'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
Expand Down Expand Up @@ -1506,25 +1487,14 @@ def open(self, mode='r', buffering=-1, encoding=None,
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)
Expand Down Expand Up @@ -2217,9 +2187,9 @@ def test_iterdir_nodir(self):

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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Revert addition of :meth:`!pathlib.Path.scandir`. This method was added in
3.14.0a2.

0 comments on commit f51afae

Please sign in to comment.