From 2767f7065ff456e72521a268b3969e53b9b9e301 Mon Sep 17 00:00:00 2001 From: Jon Burdo Date: Tue, 3 Jan 2023 02:15:19 -0500 Subject: [PATCH 01/10] adopt old os.walk behavior to handle changes to dirs list after iteration over it has begun --- Lib/os.py | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index 598c9e502301f7..d43c28e09e6158 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -341,13 +341,36 @@ def walk(top, topdown=True, onerror=None, followlinks=False): """ sys.audit("os.walk", top, topdown, onerror, followlinks) - stack = [fspath(top)] + parent = b"" if isinstance(top, bytes) else "" + dirs = iter([fspath(top)]) + stack = [[parent, dirs]] if topdown else [dirs] islink, join = path.islink, path.join while stack: - top = stack.pop() - if isinstance(top, tuple): - yield top - continue + value = stack[-1] + if topdown: + parent, dirs = value + try: + dirname = next(dirs) + except StopIteration: + stack.pop() + continue + top = join(parent, dirname) + # bpo-23605: os.path.islink() is used instead of caching + # entry.is_symlink() result during the loop on os.scandir() because + # the caller can replace the directory entry during the previous + # "yield" + if not (followlinks or not islink(top)): + continue + else: + if isinstance(value, tuple): + stack.pop() + yield value + continue + try: + top = next(value) + except StopIteration: + stack.pop() + continue dirs = [] nondirs = [] @@ -415,20 +438,12 @@ def walk(top, topdown=True, onerror=None, followlinks=False): # Yield before sub-directory traversal if going top down yield top, dirs, nondirs # Traverse into sub-directories - for dirname in reversed(dirs): - new_path = join(top, dirname) - # bpo-23605: os.path.islink() is used instead of caching - # entry.is_symlink() result during the loop on os.scandir() because - # the caller can replace the directory entry during the "yield" - # above. - if followlinks or not islink(new_path): - stack.append(new_path) + stack.append([top, iter(dirs)]) else: # Yield after sub-directory traversal if going bottom up stack.append((top, dirs, nondirs)) # Traverse into sub-directories - for new_path in reversed(walk_dirs): - stack.append(new_path) + stack.append(iter(walk_dirs)) __all__.append("walk") From 04a16a22f5c7cd969c46e9a0c510e66f86bfc432 Mon Sep 17 00:00:00 2001 From: Jon Burdo Date: Tue, 3 Jan 2023 13:23:44 -0500 Subject: [PATCH 02/10] move stack logic to bottom of loop in os.walk --- Lib/os.py | 164 +++++++++++++++++++++++++++--------------------------- 1 file changed, 83 insertions(+), 81 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index d43c28e09e6158..bfad85a6b417fe 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -341,40 +341,14 @@ def walk(top, topdown=True, onerror=None, followlinks=False): """ sys.audit("os.walk", top, topdown, onerror, followlinks) - parent = b"" if isinstance(top, bytes) else "" - dirs = iter([fspath(top)]) - stack = [[parent, dirs]] if topdown else [dirs] + top = fspath(top) + stack = [] islink, join = path.islink, path.join - while stack: - value = stack[-1] - if topdown: - parent, dirs = value - try: - dirname = next(dirs) - except StopIteration: - stack.pop() - continue - top = join(parent, dirname) - # bpo-23605: os.path.islink() is used instead of caching - # entry.is_symlink() result during the loop on os.scandir() because - # the caller can replace the directory entry during the previous - # "yield" - if not (followlinks or not islink(top)): - continue - else: - if isinstance(value, tuple): - stack.pop() - yield value - continue - try: - top = next(value) - except StopIteration: - stack.pop() - continue - + while True: dirs = [] nondirs = [] walk_dirs = [] + cont = False # We may not have read permission for top, in which case we can't # get a list of the files the directory contains. @@ -386,64 +360,92 @@ def walk(top, topdown=True, onerror=None, followlinks=False): except OSError as error: if onerror is not None: onerror(error) - continue + cont = True + else: + with scandir_it: + while True: + try: + try: + entry = next(scandir_it) + except StopIteration: + break + except OSError as error: + if onerror is not None: + onerror(error) + cont = True + break + + try: + is_dir = entry.is_dir() + except OSError: + # If is_dir() raises an OSError, consider the entry not to + # be a directory, same behaviour as os.path.isdir(). + is_dir = False + + if is_dir: + dirs.append(entry.name) + else: + nondirs.append(entry.name) + + if not topdown and is_dir: + # Bottom-up: traverse 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 the + # entry not to be a symbolic link, same behaviour + # as os.path.islink(). + is_symlink = False + walk_into = not is_symlink + + if walk_into: + walk_dirs.append(entry.path) + if topdown: + if not cont: + # Yield before sub-directory traversal if going top down + yield top, dirs, nondirs + # Traverse into sub-directories + stack.append([top, iter(dirs)]) - cont = False - with scandir_it: while True: try: - try: - entry = next(scandir_it) - except StopIteration: + value, dirs = stack[-1] + except IndexError: + return + for dirname in dirs: + top = join(value, dirname) + # bpo-23605: os.path.islink() is used instead of caching + # entry.is_symlink() result during the loop on os.scandir() because + # the caller can replace the directory entry during the previous + # "yield" + if followlinks or not islink(top): break - except OSError as error: - if onerror is not None: - onerror(error) - cont = True - break + else: + stack.pop() + continue + break + else: + if not cont: + # Traverse into sub-directories + stack.append(((top, dirs, nondirs), iter(walk_dirs))) + while True: try: - is_dir = entry.is_dir() - except OSError: - # If is_dir() raises an OSError, consider the entry not to - # be a directory, same behaviour as os.path.isdir(). - is_dir = False - - if is_dir: - dirs.append(entry.name) + value, dirs = stack[-1] + except IndexError: + return + try: + top = next(dirs) + except StopIteration: + stack.pop() + # Yield after sub-directory traversal if going bottom up + yield value else: - nondirs.append(entry.name) - - if not topdown and is_dir: - # Bottom-up: traverse 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 the - # entry not to be a symbolic link, same behaviour - # as os.path.islink(). - is_symlink = False - walk_into = not is_symlink - - if walk_into: - walk_dirs.append(entry.path) - if cont: - continue - - if topdown: - # Yield before sub-directory traversal if going top down - yield top, dirs, nondirs - # Traverse into sub-directories - stack.append([top, iter(dirs)]) - else: - # Yield after sub-directory traversal if going bottom up - stack.append((top, dirs, nondirs)) - # Traverse into sub-directories - stack.append(iter(walk_dirs)) + break __all__.append("walk") From 6b0465ff4ffb03cd85b19f7a434c100e5bca4ee5 Mon Sep 17 00:00:00 2001 From: Jon Burdo Date: Tue, 3 Jan 2023 13:41:40 -0500 Subject: [PATCH 03/10] move scandir logic to bottom of loop --- Lib/os.py | 104 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index bfad85a6b417fe..af5e61f68300d7 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -342,6 +342,11 @@ def walk(top, topdown=True, onerror=None, followlinks=False): sys.audit("os.walk", top, topdown, onerror, followlinks) top = fspath(top) + try: + scandir_it = scandir(top) + except OSError as error: + if onerror is not None: + onerror(error) stack = [] islink, join = path.islink, path.join while True: @@ -355,55 +360,48 @@ def walk(top, topdown=True, onerror=None, followlinks=False): # We suppress the exception here, rather than blow up for a # minor reason when (say) a thousand readable directories are still # left to visit. - try: - scandir_it = scandir(top) - except OSError as error: - if onerror is not None: - onerror(error) - cont = True - else: - with scandir_it: - while True: + with scandir_it: + while True: + try: try: - try: - entry = next(scandir_it) - except StopIteration: - break - except OSError as error: - if onerror is not None: - onerror(error) - cont = True + entry = next(scandir_it) + except StopIteration: break + except OSError as error: + if onerror is not None: + onerror(error) + cont = True + break - try: - is_dir = entry.is_dir() - except OSError: - # If is_dir() raises an OSError, consider the entry not to - # be a directory, same behaviour as os.path.isdir(). - is_dir = False - - if is_dir: - dirs.append(entry.name) - else: - nondirs.append(entry.name) + try: + is_dir = entry.is_dir() + except OSError: + # If is_dir() raises an OSError, consider the entry not to + # be a directory, same behaviour as os.path.isdir(). + is_dir = False - if not topdown and is_dir: - # Bottom-up: traverse 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 the - # entry not to be a symbolic link, same behaviour - # as os.path.islink(). - is_symlink = False - walk_into = not is_symlink - - if walk_into: - walk_dirs.append(entry.path) + if is_dir: + dirs.append(entry.name) + else: + nondirs.append(entry.name) + + if not topdown and is_dir: + # Bottom-up: traverse 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 the + # entry not to be a symbolic link, same behaviour + # as os.path.islink(). + is_symlink = False + walk_into = not is_symlink + + if walk_into: + walk_dirs.append(entry.path) if topdown: if not cont: # Yield before sub-directory traversal if going top down @@ -423,7 +421,13 @@ def walk(top, topdown=True, onerror=None, followlinks=False): # the caller can replace the directory entry during the previous # "yield" if followlinks or not islink(top): - break + try: + scandir_it = scandir(top) + except OSError as error: + if onerror is not None: + onerror(error) + else: + break else: stack.pop() continue @@ -445,7 +449,13 @@ def walk(top, topdown=True, onerror=None, followlinks=False): # Yield after sub-directory traversal if going bottom up yield value else: - break + try: + scandir_it = scandir(top) + except OSError as error: + if onerror is not None: + onerror(error) + else: + break __all__.append("walk") From 2ff800fef05a24af88b0dbb6a9c823c37a6f93ba Mon Sep 17 00:00:00 2001 From: Jon Burdo Date: Tue, 3 Jan 2023 14:09:01 -0500 Subject: [PATCH 04/10] use for-loop to process dirs in bottom-up clause --- Lib/os.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index af5e61f68300d7..ed3207379238db 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -442,13 +442,7 @@ def walk(top, topdown=True, onerror=None, followlinks=False): value, dirs = stack[-1] except IndexError: return - try: - top = next(dirs) - except StopIteration: - stack.pop() - # Yield after sub-directory traversal if going bottom up - yield value - else: + for top in dirs: try: scandir_it = scandir(top) except OSError as error: @@ -456,6 +450,12 @@ def walk(top, topdown=True, onerror=None, followlinks=False): onerror(error) else: break + else: + stack.pop() + # Yield after sub-directory traversal if going bottom up + yield value + continue + break __all__.append("walk") From 41d7463c5e8a873e8625422af8cee963d5414a57 Mon Sep 17 00:00:00 2001 From: Jon Burdo Date: Tue, 3 Jan 2023 15:19:54 -0500 Subject: [PATCH 05/10] return if scandir_it fails on first value --- Lib/os.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/os.py b/Lib/os.py index ed3207379238db..b54f48346d8c1e 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -347,6 +347,8 @@ def walk(top, topdown=True, onerror=None, followlinks=False): except OSError as error: if onerror is not None: onerror(error) + return + stack = [] islink, join = path.islink, path.join while True: From d81ff45aaafb43900dd09b9d6f180b77ea0c0780 Mon Sep 17 00:00:00 2001 From: Jon Burdo Date: Tue, 3 Jan 2023 17:35:37 -0500 Subject: [PATCH 06/10] add test for late modification of dirs in os.walk --- Lib/test/test_os.py | 57 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 58e04dd1348fd1..792361962280e6 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1512,6 +1512,57 @@ def test_walk_above_recursion_limit(self): self.assertEqual(sorted(dirs), ["SUB1", "SUB2", "d"]) self.assertEqual(all, expected) + def test_walk_late_dirs_modification(self): + extra_parts = [ + ("SUB3", "SUB31"), + ("SUB4", "SUB41"), + ("SUB5", "SUB51"), + ] + for dir_parts in extra_parts: + os.makedirs(os.path.join(self.walk_path, *dir_parts)) + + all = self.walk(self.walk_path) + result = [] + result.append(next(all)) + dirs = result[0][1] + dirs.sort() + + # SUB1 + # SUB1/SUB11 + result.append(next(all)) + result.append(next(all)) + # SUB2 is removed + dirs.pop(1) + # swap SUB3 and SUB4 + dirs[1], dirs[2] = dirs[2], dirs[1] + # SUB4 + # SUB4/SUB41 + # SUB3 + result.append(next(all)) + result.append(next(all)) + result.append(next(all)) + # clear while between SUB3 and SUB3/SUB31 + dirs.clear() + result.extend(all) + + expected_parts = [ + ([], [], ["tmp1"]), + (["SUB1"], ["SUB11"], ["tmp2"]), + (["SUB1", "SUB11"], [], []), + # SUB2 skipped + (["SUB4"], ["SUB41"], []), + (["SUB4", "SUB41"], [], []), + (["SUB3"], ["SUB31"], []), + (["SUB3", "SUB31"], [], []), + # SUB5 skipped + # SUB5/SUB51 skipped + ] + expected = [ + (os.path.join(self.walk_path, *r), d, f) + for r, d, f in expected_parts + ] + self.assertEqual(result, expected) + @unittest.skipUnless(hasattr(os, 'fwalk'), "Test needs os.fwalk()") class FwalkTests(WalkTests): @@ -1603,6 +1654,10 @@ def walk(self, top, **kwargs): bdirs[:] = list(map(os.fsencode, dirs)) bfiles[:] = list(map(os.fsencode, files)) + # modifying bdirs in self.walk above prevents late modification + test_walk_late_dirs_modification = None + + @unittest.skipUnless(hasattr(os, 'fwalk'), "Test needs os.fwalk()") class BytesFwalkTests(FwalkTests): """Tests for os.walk() with bytes.""" @@ -1615,6 +1670,8 @@ def fwalk(self, top='.', *args, **kwargs): bdirs[:] = list(map(os.fsencode, dirs)) bfiles[:] = list(map(os.fsencode, files)) + test_walk_late_dirs_modification = None + class MakedirTests(unittest.TestCase): def setUp(self): From 2829a7dc11c2ba2e370e9a1d03464966d5019a9c Mon Sep 17 00:00:00 2001 From: Jon Burdo Date: Tue, 3 Jan 2023 18:04:14 -0500 Subject: [PATCH 07/10] use while-else instead of while True, make variable names clearer --- Lib/os.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index b54f48346d8c1e..6f38e38bbeb250 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -355,7 +355,7 @@ def walk(top, topdown=True, onerror=None, followlinks=False): dirs = [] nondirs = [] walk_dirs = [] - cont = False + use_entry = True # We may not have read permission for top, in which case we can't # get a list of the files the directory contains. @@ -372,7 +372,7 @@ def walk(top, topdown=True, onerror=None, followlinks=False): except OSError as error: if onerror is not None: onerror(error) - cont = True + use_entry = False break try: @@ -405,19 +405,17 @@ def walk(top, topdown=True, onerror=None, followlinks=False): if walk_into: walk_dirs.append(entry.path) if topdown: - if not cont: + if use_entry: # Yield before sub-directory traversal if going top down yield top, dirs, nondirs - # Traverse into sub-directories + # Append dir names to walk along with top, their parent dir stack.append([top, iter(dirs)]) - while True: - try: - value, dirs = stack[-1] - except IndexError: - return + # Set top and scandir_it for the next iteration + while stack: + root, dirs = stack[-1] for dirname in dirs: - top = join(value, dirname) + top = join(root, dirname) # bpo-23605: os.path.islink() is used instead of caching # entry.is_symlink() result during the loop on os.scandir() because # the caller can replace the directory entry during the previous @@ -434,16 +432,18 @@ def walk(top, topdown=True, onerror=None, followlinks=False): stack.pop() continue break + else: + return else: - if not cont: - # Traverse into sub-directories + if use_entry: + # Traverse into sub-directories by appending a value + # (entry, dirs) where dirs will be walked and then + # when dirs is exhausted entry will be yielded stack.append(((top, dirs, nondirs), iter(walk_dirs))) - while True: - try: - value, dirs = stack[-1] - except IndexError: - return + # Set top and scandir_it for the next iteration + while stack: + entry, dirs = stack[-1] for top in dirs: try: scandir_it = scandir(top) @@ -454,10 +454,11 @@ def walk(top, topdown=True, onerror=None, followlinks=False): break else: stack.pop() - # Yield after sub-directory traversal if going bottom up - yield value + yield entry continue break + else: + return __all__.append("walk") From b59358b46af47e2895abfbaf9169482b71173bd1 Mon Sep 17 00:00:00 2001 From: Jon Burdo Date: Tue, 3 Jan 2023 18:06:13 -0500 Subject: [PATCH 08/10] change os.walk docstring to say "traverse" instead of "recurse" --- Lib/os.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/os.py b/Lib/os.py index 6f38e38bbeb250..ab97570208cc02 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -302,7 +302,7 @@ def walk(top, topdown=True, onerror=None, followlinks=False): subdirectories (directories are generated bottom up). When topdown is true, the caller can modify the dirnames list in-place - (e.g., via del or slice assignment), and walk will only recurse into the + (e.g., via del or slice assignment), and walk will only traverse into the subdirectories whose names remain in dirnames; this can be used to prune the search, or to impose a specific order of visiting. Modifying dirnames when topdown is false has no effect on the behavior of os.walk(), since the From 839f31eb34e5ecc4ddf5cb0060487483fb788513 Mon Sep 17 00:00:00 2001 From: Jon Burdo Date: Wed, 4 Jan 2023 15:19:02 -0500 Subject: [PATCH 09/10] fix scandir comment --- Lib/os.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index ab97570208cc02..959d22f42ada7f 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -342,6 +342,12 @@ def walk(top, topdown=True, onerror=None, followlinks=False): sys.audit("os.walk", top, topdown, onerror, followlinks) top = fspath(top) + + # We may not have read permission for top, in which case we can't + # get a list of the files the directory contains. + # We suppress the exception here, rather than blow up for a + # minor reason when (say) a thousand readable directories are still + # left to visit. try: scandir_it = scandir(top) except OSError as error: @@ -357,11 +363,6 @@ def walk(top, topdown=True, onerror=None, followlinks=False): walk_dirs = [] use_entry = True - # We may not have read permission for top, in which case we can't - # get a list of the files the directory contains. - # We suppress the exception here, rather than blow up for a - # minor reason when (say) a thousand readable directories are still - # left to visit. with scandir_it: while True: try: From 80b7e825bced8e9bd256d6f154822695fc363f75 Mon Sep 17 00:00:00 2001 From: Jon Burdo Date: Wed, 4 Jan 2023 17:08:34 -0500 Subject: [PATCH 10/10] split topdown and bottomup logic into separate functions for os.walk --- Lib/os.py | 161 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 96 insertions(+), 65 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index 959d22f42ada7f..d14878552e67ab 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -340,14 +340,12 @@ def walk(top, topdown=True, onerror=None, followlinks=False): """ sys.audit("os.walk", top, topdown, onerror, followlinks) - top = fspath(top) + if topdown: + return _walk_topdown(top, onerror, followlinks) + return _walk_bottomup(top, onerror, followlinks) - # We may not have read permission for top, in which case we can't - # get a list of the files the directory contains. - # We suppress the exception here, rather than blow up for a - # minor reason when (say) a thousand readable directories are still - # left to visit. +def _walk_topdown(top, onerror, followlinks): try: scandir_it = scandir(top) except OSError as error: @@ -360,16 +358,13 @@ def walk(top, topdown=True, onerror=None, followlinks=False): while True: dirs = [] nondirs = [] - walk_dirs = [] use_entry = True - with scandir_it: while True: try: - try: - entry = next(scandir_it) - except StopIteration: - break + entry = next(scandir_it) + except StopIteration: + break except OSError as error: if onerror is not None: onerror(error) @@ -388,7 +383,72 @@ def walk(top, topdown=True, onerror=None, followlinks=False): else: nondirs.append(entry.name) - if not topdown and is_dir: + if use_entry: + # Yield before sub-directory traversal if going top down + yield top, dirs, nondirs + # Append dir names to walk along with top, their parent dir + stack.append([top, iter(dirs)]) + + # Set top and scandir_it for the next iteration + while stack: + root, dirname_iter = stack[-1] + for dirname in dirname_iter: + top = join(root, dirname) + # bpo-23605: os.path.islink() is used instead of caching + # entry.is_symlink() result during the loop on os.scandir() because + # the caller can replace the directory entry during the previous + # "yield" + if followlinks or not islink(top): + try: + scandir_it = scandir(top) + except OSError as error: + if onerror is not None: + onerror(error) + else: + break + else: + stack.pop() + continue + break + else: + return + +def _walk_bottomup(top, onerror, followlinks): + try: + scandir_it = scandir(top) + except OSError as error: + if onerror is not None: + onerror(error) + return + + stack = [] + islink, join = path.islink, path.join + while True: + dirs = [] + nondirs = [] + walk_dirs = [] + use_entry = True + with scandir_it: + while True: + try: + entry = next(scandir_it) + except StopIteration: + break + except OSError as error: + if onerror is not None: + onerror(error) + use_entry = False + break + + try: + is_dir = entry.is_dir() + except OSError: + # If is_dir() raises an OSError, consider the entry not to + # be a directory, same behaviour as os.path.isdir(). + is_dir = False + + if is_dir: + dirs.append(entry.name) # Bottom-up: traverse into sub-directory, but exclude # symlinks to directories if followlinks is False if followlinks: @@ -405,61 +465,32 @@ def walk(top, topdown=True, onerror=None, followlinks=False): if walk_into: walk_dirs.append(entry.path) - if topdown: - if use_entry: - # Yield before sub-directory traversal if going top down - yield top, dirs, nondirs - # Append dir names to walk along with top, their parent dir - stack.append([top, iter(dirs)]) - - # Set top and scandir_it for the next iteration - while stack: - root, dirs = stack[-1] - for dirname in dirs: - top = join(root, dirname) - # bpo-23605: os.path.islink() is used instead of caching - # entry.is_symlink() result during the loop on os.scandir() because - # the caller can replace the directory entry during the previous - # "yield" - if followlinks or not islink(top): - try: - scandir_it = scandir(top) - except OSError as error: - if onerror is not None: - onerror(error) - else: - break else: - stack.pop() - continue - break - else: - return - else: - if use_entry: - # Traverse into sub-directories by appending a value - # (entry, dirs) where dirs will be walked and then - # when dirs is exhausted entry will be yielded - stack.append(((top, dirs, nondirs), iter(walk_dirs))) - - # Set top and scandir_it for the next iteration - while stack: - entry, dirs = stack[-1] - for top in dirs: - try: - scandir_it = scandir(top) - except OSError as error: - if onerror is not None: - onerror(error) - else: - break + nondirs.append(entry.name) + if use_entry: + # Traverse into sub-directories by appending a value + # (entry, dirs) where dirs will be walked and then + # when dirs is exhausted entry will be yielded + stack.append(((top, dirs, nondirs), iter(walk_dirs))) + + # Set top and scandir_it for the next iteration + while stack: + entry, dirpath_iter = stack[-1] + for top in dirpath_iter: + try: + scandir_it = scandir(top) + except OSError as error: + if onerror is not None: + onerror(error) else: - stack.pop() - yield entry - continue - break + break else: - return + stack.pop() + yield entry + continue + break + else: + return __all__.append("walk")