Issue #15177: Added dir_fd parameter to os.fwalk().
This commit is contained in:
parent
2a193a818a
commit
c48fe98a7c
|
@ -2211,18 +2211,21 @@ features:
|
||||||
os.rmdir(os.path.join(root, name))
|
os.rmdir(os.path.join(root, name))
|
||||||
|
|
||||||
|
|
||||||
.. function:: fwalk(top, topdown=True, onerror=None, followlinks=False)
|
.. function:: fwalk(top='.', topdown=True, onerror=None, followlinks=False, *, dir_fd=None)
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
single: directory; walking
|
single: directory; walking
|
||||||
single: directory; traversal
|
single: directory; traversal
|
||||||
|
|
||||||
This behaves exactly like :func:`walk`, except that it yields a 4-tuple
|
This behaves exactly like :func:`walk`, except that it yields a 4-tuple
|
||||||
``(dirpath, dirnames, filenames, dirfd)``.
|
``(dirpath, dirnames, filenames, dirfd)``, and it supports ``dir_fd``.
|
||||||
|
|
||||||
*dirpath*, *dirnames* and *filenames* are identical to :func:`walk` output,
|
*dirpath*, *dirnames* and *filenames* are identical to :func:`walk` output,
|
||||||
and *dirfd* is a file descriptor referring to the directory *dirpath*.
|
and *dirfd* is a file descriptor referring to the directory *dirpath*.
|
||||||
|
|
||||||
|
This function always supports :ref:`paths relative to directory descriptors
|
||||||
|
<dir_fd>`.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Since :func:`fwalk` yields file descriptors, those are only valid until
|
Since :func:`fwalk` yields file descriptors, those are only valid until
|
||||||
|
|
18
Lib/os.py
18
Lib/os.py
|
@ -422,9 +422,9 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
|
||||||
|
|
||||||
__all__.append("walk")
|
__all__.append("walk")
|
||||||
|
|
||||||
if open in supports_dir_fd:
|
if {open, stat} <= supports_dir_fd and {listdir, stat} <= supports_fd:
|
||||||
|
|
||||||
def fwalk(top, topdown=True, onerror=None, followlinks=False):
|
def fwalk(top=".", topdown=True, onerror=None, followlinks=False, *, dir_fd=None):
|
||||||
"""Directory tree generator.
|
"""Directory tree generator.
|
||||||
|
|
||||||
This behaves exactly like walk(), except that it yields a 4-tuple
|
This behaves exactly like walk(), except that it yields a 4-tuple
|
||||||
|
@ -434,9 +434,13 @@ if open in supports_dir_fd:
|
||||||
`dirpath`, `dirnames` and `filenames` are identical to walk() output,
|
`dirpath`, `dirnames` and `filenames` are identical to walk() output,
|
||||||
and `dirfd` is a file descriptor referring to the directory `dirpath`.
|
and `dirfd` is a file descriptor referring to the directory `dirpath`.
|
||||||
|
|
||||||
The advantage of walkfd() over walk() is that it's safe against symlink
|
The advantage of fwalk() over walk() is that it's safe against symlink
|
||||||
races (when followlinks is False).
|
races (when followlinks is False).
|
||||||
|
|
||||||
|
If dir_fd is not None, it should be a file descriptor open to a directory,
|
||||||
|
and top should be relative; top will then be relative to that directory.
|
||||||
|
(dir_fd is always supported for fwalk.)
|
||||||
|
|
||||||
Caution:
|
Caution:
|
||||||
Since fwalk() yields file descriptors, those are only valid until the
|
Since fwalk() yields file descriptors, those are only valid until the
|
||||||
next iteration step, so you should dup() them if you want to keep them
|
next iteration step, so you should dup() them if you want to keep them
|
||||||
|
@ -455,11 +459,11 @@ if open in supports_dir_fd:
|
||||||
"""
|
"""
|
||||||
# Note: To guard against symlink races, we use the standard
|
# Note: To guard against symlink races, we use the standard
|
||||||
# lstat()/open()/fstat() trick.
|
# lstat()/open()/fstat() trick.
|
||||||
orig_st = lstat(top)
|
orig_st = stat(top, follow_symlinks=False, dir_fd=dir_fd)
|
||||||
topfd = open(top, O_RDONLY)
|
topfd = open(top, O_RDONLY, dir_fd=dir_fd)
|
||||||
try:
|
try:
|
||||||
if (followlinks or (st.S_ISDIR(orig_st.st_mode) and
|
if (followlinks or (st.S_ISDIR(orig_st.st_mode) and
|
||||||
path.samestat(orig_st, fstat(topfd)))):
|
path.samestat(orig_st, stat(topfd)))):
|
||||||
yield from _fwalk(topfd, top, topdown, onerror, followlinks)
|
yield from _fwalk(topfd, top, topdown, onerror, followlinks)
|
||||||
finally:
|
finally:
|
||||||
close(topfd)
|
close(topfd)
|
||||||
|
@ -502,7 +506,7 @@ if open in supports_dir_fd:
|
||||||
onerror(err)
|
onerror(err)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
if followlinks or path.samestat(orig_st, fstat(dirfd)):
|
if followlinks or path.samestat(orig_st, stat(dirfd)):
|
||||||
dirpath = path.join(toppath, name)
|
dirpath = path.join(toppath, name)
|
||||||
yield from _fwalk(dirfd, dirpath, topdown, onerror, followlinks)
|
yield from _fwalk(dirfd, dirpath, topdown, onerror, followlinks)
|
||||||
finally:
|
finally:
|
||||||
|
|
|
@ -741,19 +741,38 @@ class WalkTests(unittest.TestCase):
|
||||||
class FwalkTests(WalkTests):
|
class FwalkTests(WalkTests):
|
||||||
"""Tests for os.fwalk()."""
|
"""Tests for os.fwalk()."""
|
||||||
|
|
||||||
def test_compare_to_walk(self):
|
def _compare_to_walk(self, walk_kwargs, fwalk_kwargs):
|
||||||
# compare with walk() results
|
"""
|
||||||
|
compare with walk() results.
|
||||||
|
"""
|
||||||
for topdown, followlinks in itertools.product((True, False), repeat=2):
|
for topdown, followlinks in itertools.product((True, False), repeat=2):
|
||||||
args = support.TESTFN, topdown, None, followlinks
|
d = {'topdown': topdown, 'followlinks': followlinks}
|
||||||
|
walk_kwargs.update(d)
|
||||||
|
fwalk_kwargs.update(d)
|
||||||
|
|
||||||
expected = {}
|
expected = {}
|
||||||
for root, dirs, files in os.walk(*args):
|
for root, dirs, files in os.walk(**walk_kwargs):
|
||||||
expected[root] = (set(dirs), set(files))
|
expected[root] = (set(dirs), set(files))
|
||||||
|
|
||||||
for root, dirs, files, rootfd in os.fwalk(*args):
|
for root, dirs, files, rootfd in os.fwalk(**fwalk_kwargs):
|
||||||
self.assertIn(root, expected)
|
self.assertIn(root, expected)
|
||||||
self.assertEqual(expected[root], (set(dirs), set(files)))
|
self.assertEqual(expected[root], (set(dirs), set(files)))
|
||||||
|
|
||||||
|
def test_compare_to_walk(self):
|
||||||
|
kwargs = {'top': support.TESTFN}
|
||||||
|
self._compare_to_walk(kwargs, kwargs)
|
||||||
|
|
||||||
def test_dir_fd(self):
|
def test_dir_fd(self):
|
||||||
|
try:
|
||||||
|
fd = os.open(".", os.O_RDONLY)
|
||||||
|
walk_kwargs = {'top': support.TESTFN}
|
||||||
|
fwalk_kwargs = walk_kwargs.copy()
|
||||||
|
fwalk_kwargs['dir_fd'] = fd
|
||||||
|
self._compare_to_walk(walk_kwargs, fwalk_kwargs)
|
||||||
|
finally:
|
||||||
|
os.close(fd)
|
||||||
|
|
||||||
|
def test_yields_correct_dir_fd(self):
|
||||||
# check returned file descriptors
|
# check returned file descriptors
|
||||||
for topdown, followlinks in itertools.product((True, False), repeat=2):
|
for topdown, followlinks in itertools.product((True, False), repeat=2):
|
||||||
args = support.TESTFN, topdown, None, followlinks
|
args = support.TESTFN, topdown, None, followlinks
|
||||||
|
|
|
@ -59,6 +59,8 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #15177: Added dir_fd parameter to os.fwalk().
|
||||||
|
|
||||||
- Issue #15176: Clarified behavior, documentation, and implementation
|
- Issue #15176: Clarified behavior, documentation, and implementation
|
||||||
of os.listdir().
|
of os.listdir().
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue