diff -r ec12fbf449a5 Doc/library/os.rst --- a/Doc/library/os.rst Mon Feb 08 17:57:22 2016 +0200 +++ b/Doc/library/os.rst Mon Feb 08 19:40:34 2016 +0200 @@ -1582,7 +1582,7 @@ features: .. seealso:: - The :func:`scandir` function returns directory entries along with + The :class:`scandir` function returns directory entries along with file attribute information, giving better performance for many common use cases. @@ -1868,13 +1868,13 @@ features: The *dir_fd* parameter. -.. function:: scandir(path='.') +.. class:: scandir(path='.') Return an iterator of :class:`DirEntry` objects corresponding to the entries in the directory given by *path*. The entries are yielded in arbitrary order, and the special entries ``'.'`` and ``'..'`` are not included. - Using :func:`scandir` instead of :func:`listdir` can significantly + Using :class:`scandir` instead of :func:`listdir` can significantly increase the performance of code that also needs file type or file attribute information, because :class:`DirEntry` objects expose this information if the operating system provides it when scanning a directory. @@ -1891,18 +1891,33 @@ features: :attr:`~DirEntry.path` attributes of each :class:`DirEntry` will be of the same type as *path*. - The following example shows a simple use of :func:`scandir` to display all + :class:`scandir` supports the :term:`context manager` protocol and has + the following method: + + .. method:: close() + + Close the iterator and free acquired resources. + + This is called automatically when the iterator is exhausted or garbage + collected, or when an error happened during iterating. However it + is advisable to call it explicitly or use the :keyword:`with` + statement. + + .. versionadded:: 3.6 + + The following example shows a simple use of :class:`scandir` to display all the files (excluding directories) in the given *path* that don't start with ``'.'``. The ``entry.is_file()`` call will generally not make an additional system call:: - for entry in os.scandir(path): - if not entry.name.startswith('.') and entry.is_file(): - print(entry.name) + with os.scandir(path) as it: + for entry in it: + if not entry.name.startswith('.') and entry.is_file(): + print(entry.name) .. note:: - On Unix-based systems, :func:`scandir` uses the system's + On Unix-based systems, :class:`scandir` uses the system's `opendir() `_ and `readdir() `_ @@ -1917,16 +1932,16 @@ features: .. class:: DirEntry - Object yielded by :func:`scandir` to expose the file path and other file + Object yielded by :class:`scandir` to expose the file path and other file attributes of a directory entry. - :func:`scandir` will provide as much of this information as possible without + :class:`scandir` will provide as much of this information as possible without making additional system calls. When a ``stat()`` or ``lstat()`` system call is made, the ``DirEntry`` object will cache the result. ``DirEntry`` instances are not intended to be stored in long-lived data structures; if you know the file metadata has changed or if a long time has - elapsed since calling :func:`scandir`, call ``os.stat(entry.path)`` to fetch + elapsed since calling :class:`scandir`, call ``os.stat(entry.path)`` to fetch up-to-date information. Because the ``DirEntry`` methods can make operating system calls, they may @@ -1938,22 +1953,22 @@ features: .. attribute:: name - The entry's base filename, relative to the :func:`scandir` *path* + The entry's base filename, relative to the :class:`scandir` *path* argument. The :attr:`name` attribute will be of the same type (``str`` or - ``bytes``) as the :func:`scandir` *path* argument. Use + ``bytes``) as the :class:`scandir` *path* argument. Use :func:`~os.fsdecode` to decode byte filenames. .. attribute:: path The entry's full path name: equivalent to ``os.path.join(scandir_path, - entry.name)`` where *scandir_path* is the :func:`scandir` *path* - argument. The path is only absolute if the :func:`scandir` *path* + entry.name)`` where *scandir_path* is the :class:`scandir` *path* + argument. The path is only absolute if the :class:`scandir` *path* argument was absolute. The :attr:`path` attribute will be of the same type (``str`` or - ``bytes``) as the :func:`scandir` *path* argument. Use + ``bytes``) as the :class:`scandir` *path* argument. Use :func:`~os.fsdecode` to decode byte filenames. .. method:: inode() diff -r ec12fbf449a5 Doc/whatsnew/3.5.rst --- a/Doc/whatsnew/3.5.rst Mon Feb 08 17:57:22 2016 +0200 +++ b/Doc/whatsnew/3.5.rst Mon Feb 08 19:40:34 2016 +0200 @@ -1435,8 +1435,8 @@ to perform matrix multiplication. os -- -The new :func:`~os.scandir` function returning an iterator of -:class:`~os.DirEntry` objects has been added. If possible, :func:`~os.scandir` +The new :class:`~os.scandir` function returning an iterator of +:class:`~os.DirEntry` objects has been added. If possible, :class:`~os.scandir` extracts file attributes while scanning a directory, removing the need to perform subsequent system calls to determine file type or attributes, which may significantly improve performance. (Contributed by Ben Hoyt with the help diff -r ec12fbf449a5 Doc/whatsnew/3.6.rst --- a/Doc/whatsnew/3.6.rst Mon Feb 08 17:57:22 2016 +0200 +++ b/Doc/whatsnew/3.6.rst Mon Feb 08 19:40:34 2016 +0200 @@ -104,6 +104,14 @@ directives ``%G``, ``%u`` and ``%V``. (Contributed by Ashley Anderson in :issue:`12006`.) +os +-- + +A new :meth:`scandir.close() ` method allows explicit +closing a :class:`~os.scandir` instance. The :class:`~os.scandir` class +now supports the :term:`context manager` protocol. +(Contributed by Serhiy Storchaka in :issue:`25994`.) + pickle ------ diff -r ec12fbf449a5 Lib/os.py --- a/Lib/os.py Mon Feb 08 17:57:22 2016 +0200 +++ b/Lib/os.py Mon Feb 08 19:40:34 2016 +0200 @@ -374,46 +374,47 @@ def walk(top, topdown=True, onerror=None onerror(error) return - while True: - try: + with scandir_it: + while True: try: - entry = next(scandir_it) - except StopIteration: - break - except OSError as error: - if onerror is not None: - onerror(error) - return + try: + entry = next(scandir_it) + except StopIteration: + break + except OSError as error: + if onerror is not None: + onerror(error) + return - try: - is_dir = entry.is_dir() - except OSError: - # If is_dir() raises an OSError, consider that the entry is not - # a directory, same behaviour than os.path.isdir(). - is_dir = False + try: + is_dir = entry.is_dir() + except OSError: + # If is_dir() raises an OSError, consider that the entry is not + # a directory, same behaviour than os.path.isdir(). + is_dir = False - if is_dir: - dirs.append(entry.name) - else: - nondirs.append(entry.name) + if is_dir: + dirs.append(entry.name) + else: + nondirs.append(entry.name) - if not topdown and is_dir: - # Bottom-up: recurse into sub-directory, but exclude symlinks to - # directories if followlinks is False - if followlinks: - walk_into = True - else: - try: - is_symlink = entry.is_symlink() - except OSError: - # If is_symlink() raises an OSError, consider that the - # entry is not a symbolic link, same behaviour than - # os.path.islink(). - is_symlink = False - walk_into = not is_symlink + if not topdown and is_dir: + # Bottom-up: recurse into sub-directory, but exclude symlinks to + # directories if followlinks is False + if followlinks: + walk_into = True + else: + try: + is_symlink = entry.is_symlink() + except OSError: + # If is_symlink() raises an OSError, consider that the + # entry is not a symbolic link, same behaviour than + # os.path.islink(). + is_symlink = False + walk_into = not is_symlink - if walk_into: - yield from walk(entry.path, topdown, onerror, followlinks) + if walk_into: + yield from walk(entry.path, topdown, onerror, followlinks) # Yield before recursion if going top down if topdown: @@ -442,10 +443,19 @@ class _DummyDirEntry: def is_symlink(self): return path.islink(self.path) -def _dummy_scandir(dir): +class _dummy_scandir: # listdir-based implementation for bytes patches on Windows - for name in listdir(dir): - yield _DummyDirEntry(dir, name) + def __init__(self, dir): + self.dir = dir + self.it = iter(listdir(dir)) + def __iter__(self): + return self + def __next__(self): + return _DummyDirEntry(self.dir, next(self.it)) + def __enter__(self): + return self + def __exit__(self, *args): + self.it = iter(()) __all__.append("walk") diff -r ec12fbf449a5 Misc/NEWS --- a/Misc/NEWS Mon Feb 08 17:57:22 2016 +0200 +++ b/Misc/NEWS Mon Feb 08 19:40:34 2016 +0200 @@ -170,6 +170,9 @@ Core and Builtins Library ------- +- Issue #25994: Added the close() method and the support of the context manager + protocol for the os.scandir() class. + - Issue #26117: The os.scandir() iterator now closes file descriptor not only when the iteration is finished, but when it was failed with error. diff -r ec12fbf449a5 Modules/posixmodule.c --- a/Modules/posixmodule.c Mon Feb 08 17:57:22 2016 +0200 +++ b/Modules/posixmodule.c Mon Feb 08 19:40:34 2016 +0200 @@ -11938,7 +11938,7 @@ typedef struct { #ifdef MS_WINDOWS static void -ScandirIterator_close(ScandirIterator *iterator) +ScandirIterator_closedir(ScandirIterator *iterator) { if (iterator->handle == INVALID_HANDLE_VALUE) return; @@ -11987,14 +11987,14 @@ ScandirIterator_iternext(ScandirIterator } /* Error or no more files */ - ScandirIterator_close(iterator); + ScandirIterator_closedir(iterator); return NULL; } #else /* POSIX */ static void -ScandirIterator_close(ScandirIterator *iterator) +ScandirIterator_closedir(ScandirIterator *iterator) { if (!iterator->dirp) return; @@ -12051,21 +12051,48 @@ ScandirIterator_iternext(ScandirIterator } /* Error or no more files */ - ScandirIterator_close(iterator); + ScandirIterator_closedir(iterator); return NULL; } #endif +static PyObject * +ScandirIterator_close(ScandirIterator *self, PyObject *args) +{ + ScandirIterator_closedir(self); + Py_RETURN_NONE; +} + +static PyObject * +ScandirIterator_enter(PyObject *self, PyObject *args) +{ + Py_INCREF(self); + return self; +} + +static PyObject * +ScandirIterator_exit(ScandirIterator *self, PyObject *args) +{ + ScandirIterator_closedir(self); + Py_RETURN_NONE; +} + static void ScandirIterator_dealloc(ScandirIterator *iterator) { - ScandirIterator_close(iterator); + ScandirIterator_closedir(iterator); Py_XDECREF(iterator->path.object); path_cleanup(&iterator->path); Py_TYPE(iterator)->tp_free((PyObject *)iterator); } +static PyMethodDef ScandirIterator_methods[] = { + {"__enter__", (PyCFunction)ScandirIterator_enter, METH_NOARGS}, + {"__exit__", (PyCFunction)ScandirIterator_exit, METH_VARARGS}, + {"close", (PyCFunction)ScandirIterator_close, METH_NOARGS}, +}; + static PyTypeObject ScandirIteratorType = { PyVarObject_HEAD_INIT(NULL, 0) MODNAME ".ScandirIterator", /* tp_name */ @@ -12095,6 +12122,7 @@ static PyTypeObject ScandirIteratorType 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ (iternextfunc)ScandirIterator_iternext, /* tp_iternext */ + ScandirIterator_methods, /* tp_methods */ }; static PyObject *