@@ -340,89 +340,95 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
340340
341341 """
342342 sys .audit ("os.walk" , top , topdown , onerror , followlinks )
343- return _walk (fspath (top ), topdown , onerror , followlinks )
344-
345- def _walk (top , topdown , onerror , followlinks ):
346- dirs = []
347- nondirs = []
348- walk_dirs = []
349-
350- # We may not have read permission for top, in which case we can't
351- # get a list of the files the directory contains. os.walk
352- # always suppressed the exception then, rather than blow up for a
353- # minor reason when (say) a thousand readable directories are still
354- # left to visit. That logic is copied here.
355- try :
356- # Note that scandir is global in this module due
357- # to earlier import-*.
358- scandir_it = scandir (top )
359- except OSError as error :
360- if onerror is not None :
361- onerror (error )
362- return
363343
364- with scandir_it :
365- while True :
366- try :
344+ stack = [(False , fspath (top ))]
345+ islink , join = path .islink , path .join
346+ while stack :
347+ must_yield , top = stack .pop ()
348+ if must_yield :
349+ yield top
350+ continue
351+
352+ dirs = []
353+ nondirs = []
354+ walk_dirs = []
355+
356+ # We may not have read permission for top, in which case we can't
357+ # get a list of the files the directory contains.
358+ # We suppress the exception here, rather than blow up for a
359+ # minor reason when (say) a thousand readable directories are still
360+ # left to visit.
361+ try :
362+ scandir_it = scandir (top )
363+ except OSError as error :
364+ if onerror is not None :
365+ onerror (error )
366+ continue
367+
368+ cont = False
369+ with scandir_it :
370+ while True :
367371 try :
368- entry = next (scandir_it )
369- except StopIteration :
372+ try :
373+ entry = next (scandir_it )
374+ except StopIteration :
375+ break
376+ except OSError as error :
377+ if onerror is not None :
378+ onerror (error )
379+ cont = True
370380 break
371- except OSError as error :
372- if onerror is not None :
373- onerror (error )
374- return
375381
376- try :
377- is_dir = entry .is_dir ()
378- except OSError :
379- # If is_dir() raises an OSError, consider that the entry is not
380- # a directory, same behaviour than os.path.isdir().
381- is_dir = False
382-
383- if is_dir :
384- dirs .append (entry .name )
385- else :
386- nondirs .append (entry .name )
382+ try :
383+ is_dir = entry .is_dir ()
384+ except OSError :
385+ # If is_dir() raises an OSError, consider the entry not to
386+ # be a directory, same behaviour as os.path.isdir().
387+ is_dir = False
387388
388- if not topdown and is_dir :
389- # Bottom-up: recurse into sub-directory, but exclude symlinks to
390- # directories if followlinks is False
391- if followlinks :
392- walk_into = True
389+ if is_dir :
390+ dirs .append (entry .name )
393391 else :
394- try :
395- is_symlink = entry .is_symlink ()
396- except OSError :
397- # If is_symlink() raises an OSError, consider that the
398- # entry is not a symbolic link, same behaviour than
399- # os.path.islink().
400- is_symlink = False
401- walk_into = not is_symlink
402-
403- if walk_into :
404- walk_dirs .append (entry .path )
405-
406- # Yield before recursion if going top down
407- if topdown :
408- yield top , dirs , nondirs
409-
410- # Recurse into sub-directories
411- islink , join = path .islink , path .join
412- for dirname in dirs :
413- new_path = join (top , dirname )
414- # Issue #23605: os.path.islink() is used instead of caching
415- # entry.is_symlink() result during the loop on os.scandir() because
416- # the caller can replace the directory entry during the "yield"
417- # above.
418- if followlinks or not islink (new_path ):
419- yield from _walk (new_path , topdown , onerror , followlinks )
420- else :
421- # Recurse into sub-directories
422- for new_path in walk_dirs :
423- yield from _walk (new_path , topdown , onerror , followlinks )
424- # Yield after recursion if going bottom up
425- yield top , dirs , nondirs
392+ nondirs .append (entry .name )
393+
394+ if not topdown and is_dir :
395+ # Bottom-up: traverse into sub-directory, but exclude
396+ # symlinks to directories if followlinks is False
397+ if followlinks :
398+ walk_into = True
399+ else :
400+ try :
401+ is_symlink = entry .is_symlink ()
402+ except OSError :
403+ # If is_symlink() raises an OSError, consider the
404+ # entry not to be a symbolic link, same behaviour
405+ # as os.path.islink().
406+ is_symlink = False
407+ walk_into = not is_symlink
408+
409+ if walk_into :
410+ walk_dirs .append (entry .path )
411+ if cont :
412+ continue
413+
414+ if topdown :
415+ # Yield before sub-directory traversal if going top down
416+ yield top , dirs , nondirs
417+ # Traverse into sub-directories
418+ for dirname in reversed (dirs ):
419+ new_path = join (top , dirname )
420+ # bpo-23605: os.path.islink() is used instead of caching
421+ # entry.is_symlink() result during the loop on os.scandir() because
422+ # the caller can replace the directory entry during the "yield"
423+ # above.
424+ if followlinks or not islink (new_path ):
425+ stack .append ((False , new_path ))
426+ else :
427+ # Yield after sub-directory traversal if going bottom up
428+ stack .append ((True , (top , dirs , nondirs )))
429+ # Traverse into sub-directories
430+ for new_path in reversed (walk_dirs ):
431+ stack .append ((False , new_path ))
426432
427433__all__ .append ("walk" )
428434
0 commit comments