diff -r 211e29335e72 Lib/os.py --- a/Lib/os.py Fri Mar 13 11:01:30 2015 +0100 +++ b/Lib/os.py Fri Mar 13 11:34:41 2015 +0100 @@ -354,7 +354,7 @@ def walk(top, topdown=True, onerror=None dirs = [] nondirs = [] - symlinks = set() + catch_oserror = True # We may not have read permission for top, in which case we can't # get a list of the files the directory contains. os.walk @@ -377,16 +377,30 @@ def walk(top, topdown=True, onerror=None else: nondirs.append(entry.name) - if is_dir and not followlinks: - try: - if entry.is_symlink(): - symlinks.add(entry.name) - except OSError: - # If is_symlink() raises an OSError, consider that the - # entry is not a symbolik link, same behaviour than - # os.path.islink(). - pass + 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 symbolik link, same behaviour than + # os.path.islink(). + is_symlink = False + walk_into = not is_symlink + + if walk_into: + # Don't catch OSError in recursive calls to walk() + catch_oserror = False + yield from walk(entry.path, topdown, onerror, followlinks) + catch_oserror = True except OSError as error: + if not catch_oserror: + # OSError raised by the recursive call to walk() + raise # scandir() or iterating into scandir() iterator raised an OSError if onerror is not None: onerror(error) @@ -396,14 +410,18 @@ def walk(top, topdown=True, onerror=None if topdown: yield top, dirs, nondirs - # Recurse into sub-directories - for name in dirs: - if followlinks or name not in symlinks: - new_path = path.join(top, name) - yield from walk(new_path, topdown, onerror, followlinks) - - # Yield after recursion if going bottom up - if not topdown: + # Recurse into sub-directories + islink, join = path.islink, path.join + for name in dirs: + new_path = join(top, name) + # Issue #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): + yield from walk(new_path, topdown, onerror, followlinks) + else: + # Yield after recursion if going bottom up yield top, dirs, nondirs __all__.append("walk")