1"""scandir, a better directory iterator and faster os.walk(), now in the Python 3.5 stdlib 2 3scandir() is a generator version of os.listdir() that returns an 4iterator over files in a directory, and also exposes the extra 5information most OSes provide while iterating files in a directory 6(such as type and stat information). 7 8This module also includes a version of os.walk() that uses scandir() 9to speed it up significantly. 10 11See README.md or https://github.com/benhoyt/scandir for rationale and 12docs, or read PEP 471 (https://www.python.org/dev/peps/pep-0471/) for 13more details on its inclusion into Python 3.5 14 15scandir is released under the new BSD 3-clause license. 16 17Copyright (c) 2012, Ben Hoyt 18All rights reserved. 19 20Redistribution and use in source and binary forms, with or without 21modification, are permitted provided that the following conditions are met: 22 23* Redistributions of source code must retain the above copyright notice, this 24list of conditions and the following disclaimer. 25 26* Redistributions in binary form must reproduce the above copyright notice, 27this list of conditions and the following disclaimer in the documentation 28and/or other materials provided with the distribution. 29 30* Neither the name of Ben Hoyt nor the names of its contributors may be used 31to endorse or promote products derived from this software without specific 32prior written permission. 33 34THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 35AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 36IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 37DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 38FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 39DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 40SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 41CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 42OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 43OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 44""" 45 46from __future__ import division 47 48from errno import ENOENT 49from os import listdir, lstat, stat, strerror 50from os.path import join, islink 51from stat import S_IFDIR, S_IFLNK, S_IFREG 52import collections 53import sys 54 55try: 56 import _scandir 57except ImportError: 58 _scandir = None 59 60try: 61 import ctypes 62except ImportError: 63 ctypes = None 64 65if _scandir is None and ctypes is None: 66 import warnings 67 warnings.warn("scandir can't find the compiled _scandir C module " 68 "or ctypes, using slow generic fallback") 69 70__version__ = '1.10.0' 71__all__ = ['scandir', 'walk'] 72 73# Windows FILE_ATTRIBUTE constants for interpreting the 74# FIND_DATA.dwFileAttributes member 75FILE_ATTRIBUTE_ARCHIVE = 32 76FILE_ATTRIBUTE_COMPRESSED = 2048 77FILE_ATTRIBUTE_DEVICE = 64 78FILE_ATTRIBUTE_DIRECTORY = 16 79FILE_ATTRIBUTE_ENCRYPTED = 16384 80FILE_ATTRIBUTE_HIDDEN = 2 81FILE_ATTRIBUTE_INTEGRITY_STREAM = 32768 82FILE_ATTRIBUTE_NORMAL = 128 83FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 8192 84FILE_ATTRIBUTE_NO_SCRUB_DATA = 131072 85FILE_ATTRIBUTE_OFFLINE = 4096 86FILE_ATTRIBUTE_READONLY = 1 87FILE_ATTRIBUTE_REPARSE_POINT = 1024 88FILE_ATTRIBUTE_SPARSE_FILE = 512 89FILE_ATTRIBUTE_SYSTEM = 4 90FILE_ATTRIBUTE_TEMPORARY = 256 91FILE_ATTRIBUTE_VIRTUAL = 65536 92 93IS_PY3 = sys.version_info >= (3, 0) 94 95if IS_PY3: 96 unicode = str # Because Python <= 3.2 doesn't have u'unicode' syntax 97 98 99class GenericDirEntry(object): 100 __slots__ = ('name', '_stat', '_lstat', '_scandir_path', '_path') 101 102 def __init__(self, scandir_path, name): 103 self._scandir_path = scandir_path 104 self.name = name 105 self._stat = None 106 self._lstat = None 107 self._path = None 108 109 @property 110 def path(self): 111 if self._path is None: 112 self._path = join(self._scandir_path, self.name) 113 return self._path 114 115 def stat(self, follow_symlinks=True): 116 if follow_symlinks: 117 if self._stat is None: 118 self._stat = stat(self.path) 119 return self._stat 120 else: 121 if self._lstat is None: 122 self._lstat = lstat(self.path) 123 return self._lstat 124 125 # The code duplication below is intentional: this is for slightly 126 # better performance on systems that fall back to GenericDirEntry. 127 # It avoids an additional attribute lookup and method call, which 128 # are relatively slow on CPython. 129 def is_dir(self, follow_symlinks=True): 130 try: 131 st = self.stat(follow_symlinks=follow_symlinks) 132 except OSError as e: 133 if e.errno != ENOENT: 134 raise 135 return False # Path doesn't exist or is a broken symlink 136 return st.st_mode & 0o170000 == S_IFDIR 137 138 def is_file(self, follow_symlinks=True): 139 try: 140 st = self.stat(follow_symlinks=follow_symlinks) 141 except OSError as e: 142 if e.errno != ENOENT: 143 raise 144 return False # Path doesn't exist or is a broken symlink 145 return st.st_mode & 0o170000 == S_IFREG 146 147 def is_symlink(self): 148 try: 149 st = self.stat(follow_symlinks=False) 150 except OSError as e: 151 if e.errno != ENOENT: 152 raise 153 return False # Path doesn't exist or is a broken symlink 154 return st.st_mode & 0o170000 == S_IFLNK 155 156 def inode(self): 157 st = self.stat(follow_symlinks=False) 158 return st.st_ino 159 160 def __str__(self): 161 return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name) 162 163 __repr__ = __str__ 164 165 166def _scandir_generic(path=unicode('.')): 167 """Like os.listdir(), but yield DirEntry objects instead of returning 168 a list of names. 169 """ 170 for name in listdir(path): 171 yield GenericDirEntry(path, name) 172 173 174if IS_PY3 and sys.platform == 'win32': 175 def scandir_generic(path=unicode('.')): 176 if isinstance(path, bytes): 177 raise TypeError("os.scandir() doesn't support bytes path on Windows, use Unicode instead") 178 return _scandir_generic(path) 179 scandir_generic.__doc__ = _scandir_generic.__doc__ 180else: 181 scandir_generic = _scandir_generic 182 183 184scandir_c = None 185scandir_python = None 186 187 188if sys.platform == 'win32': 189 if ctypes is not None: 190 from ctypes import wintypes 191 192 # Various constants from windows.h 193 INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value 194 ERROR_FILE_NOT_FOUND = 2 195 ERROR_NO_MORE_FILES = 18 196 IO_REPARSE_TAG_SYMLINK = 0xA000000C 197 198 # Numer of seconds between 1601-01-01 and 1970-01-01 199 SECONDS_BETWEEN_EPOCHS = 11644473600 200 201 kernel32 = ctypes.windll.kernel32 202 203 # ctypes wrappers for (wide string versions of) FindFirstFile, 204 # FindNextFile, and FindClose 205 FindFirstFile = kernel32.FindFirstFileW 206 FindFirstFile.argtypes = [ 207 wintypes.LPCWSTR, 208 ctypes.POINTER(wintypes.WIN32_FIND_DATAW), 209 ] 210 FindFirstFile.restype = wintypes.HANDLE 211 212 FindNextFile = kernel32.FindNextFileW 213 FindNextFile.argtypes = [ 214 wintypes.HANDLE, 215 ctypes.POINTER(wintypes.WIN32_FIND_DATAW), 216 ] 217 FindNextFile.restype = wintypes.BOOL 218 219 FindClose = kernel32.FindClose 220 FindClose.argtypes = [wintypes.HANDLE] 221 FindClose.restype = wintypes.BOOL 222 223 Win32StatResult = collections.namedtuple('Win32StatResult', [ 224 'st_mode', 225 'st_ino', 226 'st_dev', 227 'st_nlink', 228 'st_uid', 229 'st_gid', 230 'st_size', 231 'st_atime', 232 'st_mtime', 233 'st_ctime', 234 'st_atime_ns', 235 'st_mtime_ns', 236 'st_ctime_ns', 237 'st_file_attributes', 238 ]) 239 240 def filetime_to_time(filetime): 241 """Convert Win32 FILETIME to time since Unix epoch in seconds.""" 242 total = filetime.dwHighDateTime << 32 | filetime.dwLowDateTime 243 return total / 10000000 - SECONDS_BETWEEN_EPOCHS 244 245 def find_data_to_stat(data): 246 """Convert Win32 FIND_DATA struct to stat_result.""" 247 # First convert Win32 dwFileAttributes to st_mode 248 attributes = data.dwFileAttributes 249 st_mode = 0 250 if attributes & FILE_ATTRIBUTE_DIRECTORY: 251 st_mode |= S_IFDIR | 0o111 252 else: 253 st_mode |= S_IFREG 254 if attributes & FILE_ATTRIBUTE_READONLY: 255 st_mode |= 0o444 256 else: 257 st_mode |= 0o666 258 if (attributes & FILE_ATTRIBUTE_REPARSE_POINT and 259 data.dwReserved0 == IO_REPARSE_TAG_SYMLINK): 260 st_mode ^= st_mode & 0o170000 261 st_mode |= S_IFLNK 262 263 st_size = data.nFileSizeHigh << 32 | data.nFileSizeLow 264 st_atime = filetime_to_time(data.ftLastAccessTime) 265 st_mtime = filetime_to_time(data.ftLastWriteTime) 266 st_ctime = filetime_to_time(data.ftCreationTime) 267 268 # Some fields set to zero per CPython's posixmodule.c: st_ino, st_dev, 269 # st_nlink, st_uid, st_gid 270 return Win32StatResult(st_mode, 0, 0, 0, 0, 0, st_size, 271 st_atime, st_mtime, st_ctime, 272 int(st_atime * 1000000000), 273 int(st_mtime * 1000000000), 274 int(st_ctime * 1000000000), 275 attributes) 276 277 class Win32DirEntryPython(object): 278 __slots__ = ('name', '_stat', '_lstat', '_find_data', '_scandir_path', '_path', '_inode') 279 280 def __init__(self, scandir_path, name, find_data): 281 self._scandir_path = scandir_path 282 self.name = name 283 self._stat = None 284 self._lstat = None 285 self._find_data = find_data 286 self._path = None 287 self._inode = None 288 289 @property 290 def path(self): 291 if self._path is None: 292 self._path = join(self._scandir_path, self.name) 293 return self._path 294 295 def stat(self, follow_symlinks=True): 296 if follow_symlinks: 297 if self._stat is None: 298 if self.is_symlink(): 299 # It's a symlink, call link-following stat() 300 self._stat = stat(self.path) 301 else: 302 # Not a symlink, stat is same as lstat value 303 if self._lstat is None: 304 self._lstat = find_data_to_stat(self._find_data) 305 self._stat = self._lstat 306 return self._stat 307 else: 308 if self._lstat is None: 309 # Lazily convert to stat object, because it's slow 310 # in Python, and often we only need is_dir() etc 311 self._lstat = find_data_to_stat(self._find_data) 312 return self._lstat 313 314 def is_dir(self, follow_symlinks=True): 315 is_symlink = self.is_symlink() 316 if follow_symlinks and is_symlink: 317 try: 318 return self.stat().st_mode & 0o170000 == S_IFDIR 319 except OSError as e: 320 if e.errno != ENOENT: 321 raise 322 return False 323 elif is_symlink: 324 return False 325 else: 326 return (self._find_data.dwFileAttributes & 327 FILE_ATTRIBUTE_DIRECTORY != 0) 328 329 def is_file(self, follow_symlinks=True): 330 is_symlink = self.is_symlink() 331 if follow_symlinks and is_symlink: 332 try: 333 return self.stat().st_mode & 0o170000 == S_IFREG 334 except OSError as e: 335 if e.errno != ENOENT: 336 raise 337 return False 338 elif is_symlink: 339 return False 340 else: 341 return (self._find_data.dwFileAttributes & 342 FILE_ATTRIBUTE_DIRECTORY == 0) 343 344 def is_symlink(self): 345 return (self._find_data.dwFileAttributes & 346 FILE_ATTRIBUTE_REPARSE_POINT != 0 and 347 self._find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK) 348 349 def inode(self): 350 if self._inode is None: 351 self._inode = lstat(self.path).st_ino 352 return self._inode 353 354 def __str__(self): 355 return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name) 356 357 __repr__ = __str__ 358 359 def win_error(error, filename): 360 exc = WindowsError(error, ctypes.FormatError(error)) 361 exc.filename = filename 362 return exc 363 364 def _scandir_python(path=unicode('.')): 365 """Like os.listdir(), but yield DirEntry objects instead of returning 366 a list of names. 367 """ 368 # Call FindFirstFile and handle errors 369 if isinstance(path, bytes): 370 is_bytes = True 371 filename = join(path.decode('mbcs', 'strict'), '*.*') 372 else: 373 is_bytes = False 374 filename = join(path, '*.*') 375 data = wintypes.WIN32_FIND_DATAW() 376 data_p = ctypes.byref(data) 377 handle = FindFirstFile(filename, data_p) 378 if handle == INVALID_HANDLE_VALUE: 379 error = ctypes.GetLastError() 380 if error == ERROR_FILE_NOT_FOUND: 381 # No files, don't yield anything 382 return 383 raise win_error(error, path) 384 385 # Call FindNextFile in a loop, stopping when no more files 386 try: 387 while True: 388 # Skip '.' and '..' (current and parent directory), but 389 # otherwise yield (filename, stat_result) tuple 390 name = data.cFileName 391 if name not in ('.', '..'): 392 if is_bytes: 393 name = name.encode('mbcs', 'replace') 394 yield Win32DirEntryPython(path, name, data) 395 396 data = wintypes.WIN32_FIND_DATAW() 397 data_p = ctypes.byref(data) 398 success = FindNextFile(handle, data_p) 399 if not success: 400 error = ctypes.GetLastError() 401 if error == ERROR_NO_MORE_FILES: 402 break 403 raise win_error(error, path) 404 finally: 405 if not FindClose(handle): 406 raise win_error(ctypes.GetLastError(), path) 407 408 if IS_PY3: 409 def scandir_python(path=unicode('.')): 410 if isinstance(path, bytes): 411 raise TypeError("os.scandir() doesn't support bytes path on Windows, use Unicode instead") 412 return _scandir_python(path) 413 scandir_python.__doc__ = _scandir_python.__doc__ 414 else: 415 scandir_python = _scandir_python 416 417 if _scandir is not None: 418 scandir_c = _scandir.scandir 419 DirEntry_c = _scandir.DirEntry 420 421 if _scandir is not None: 422 scandir = scandir_c 423 DirEntry = DirEntry_c 424 elif ctypes is not None: 425 scandir = scandir_python 426 DirEntry = Win32DirEntryPython 427 else: 428 scandir = scandir_generic 429 DirEntry = GenericDirEntry 430 431 432# Linux, OS X, and BSD implementation 433elif sys.platform.startswith(('linux', 'darwin', 'sunos5')) or 'bsd' in sys.platform: 434 have_dirent_d_type = (sys.platform != 'sunos5') 435 436 if ctypes is not None and have_dirent_d_type: 437 import ctypes.util 438 439 DIR_p = ctypes.c_void_p 440 441 # Rather annoying how the dirent struct is slightly different on each 442 # platform. The only fields we care about are d_name and d_type. 443 class Dirent(ctypes.Structure): 444 if sys.platform.startswith('linux'): 445 _fields_ = ( 446 ('d_ino', ctypes.c_ulong), 447 ('d_off', ctypes.c_long), 448 ('d_reclen', ctypes.c_ushort), 449 ('d_type', ctypes.c_byte), 450 ('d_name', ctypes.c_char * 256), 451 ) 452 elif 'openbsd' in sys.platform: 453 _fields_ = ( 454 ('d_ino', ctypes.c_uint64), 455 ('d_off', ctypes.c_uint64), 456 ('d_reclen', ctypes.c_uint16), 457 ('d_type', ctypes.c_uint8), 458 ('d_namlen', ctypes.c_uint8), 459 ('__d_padding', ctypes.c_uint8 * 4), 460 ('d_name', ctypes.c_char * 256), 461 ) 462 else: 463 _fields_ = ( 464 ('d_ino', ctypes.c_uint32), # must be uint32, not ulong 465 ('d_reclen', ctypes.c_ushort), 466 ('d_type', ctypes.c_byte), 467 ('d_namlen', ctypes.c_byte), 468 ('d_name', ctypes.c_char * 256), 469 ) 470 471 DT_UNKNOWN = 0 472 DT_DIR = 4 473 DT_REG = 8 474 DT_LNK = 10 475 476 Dirent_p = ctypes.POINTER(Dirent) 477 Dirent_pp = ctypes.POINTER(Dirent_p) 478 479 libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True) 480 opendir = libc.opendir 481 opendir.argtypes = [ctypes.c_char_p] 482 opendir.restype = DIR_p 483 484 readdir_r = libc.readdir_r 485 readdir_r.argtypes = [DIR_p, Dirent_p, Dirent_pp] 486 readdir_r.restype = ctypes.c_int 487 488 closedir = libc.closedir 489 closedir.argtypes = [DIR_p] 490 closedir.restype = ctypes.c_int 491 492 file_system_encoding = sys.getfilesystemencoding() 493 494 class PosixDirEntry(object): 495 __slots__ = ('name', '_d_type', '_stat', '_lstat', '_scandir_path', '_path', '_inode') 496 497 def __init__(self, scandir_path, name, d_type, inode): 498 self._scandir_path = scandir_path 499 self.name = name 500 self._d_type = d_type 501 self._inode = inode 502 self._stat = None 503 self._lstat = None 504 self._path = None 505 506 @property 507 def path(self): 508 if self._path is None: 509 self._path = join(self._scandir_path, self.name) 510 return self._path 511 512 def stat(self, follow_symlinks=True): 513 if follow_symlinks: 514 if self._stat is None: 515 if self.is_symlink(): 516 self._stat = stat(self.path) 517 else: 518 if self._lstat is None: 519 self._lstat = lstat(self.path) 520 self._stat = self._lstat 521 return self._stat 522 else: 523 if self._lstat is None: 524 self._lstat = lstat(self.path) 525 return self._lstat 526 527 def is_dir(self, follow_symlinks=True): 528 if (self._d_type == DT_UNKNOWN or 529 (follow_symlinks and self.is_symlink())): 530 try: 531 st = self.stat(follow_symlinks=follow_symlinks) 532 except OSError as e: 533 if e.errno != ENOENT: 534 raise 535 return False 536 return st.st_mode & 0o170000 == S_IFDIR 537 else: 538 return self._d_type == DT_DIR 539 540 def is_file(self, follow_symlinks=True): 541 if (self._d_type == DT_UNKNOWN or 542 (follow_symlinks and self.is_symlink())): 543 try: 544 st = self.stat(follow_symlinks=follow_symlinks) 545 except OSError as e: 546 if e.errno != ENOENT: 547 raise 548 return False 549 return st.st_mode & 0o170000 == S_IFREG 550 else: 551 return self._d_type == DT_REG 552 553 def is_symlink(self): 554 if self._d_type == DT_UNKNOWN: 555 try: 556 st = self.stat(follow_symlinks=False) 557 except OSError as e: 558 if e.errno != ENOENT: 559 raise 560 return False 561 return st.st_mode & 0o170000 == S_IFLNK 562 else: 563 return self._d_type == DT_LNK 564 565 def inode(self): 566 return self._inode 567 568 def __str__(self): 569 return '<{0}: {1!r}>'.format(self.__class__.__name__, self.name) 570 571 __repr__ = __str__ 572 573 def posix_error(filename): 574 errno = ctypes.get_errno() 575 exc = OSError(errno, strerror(errno)) 576 exc.filename = filename 577 return exc 578 579 def scandir_python(path=unicode('.')): 580 """Like os.listdir(), but yield DirEntry objects instead of returning 581 a list of names. 582 """ 583 if isinstance(path, bytes): 584 opendir_path = path 585 is_bytes = True 586 else: 587 opendir_path = path.encode(file_system_encoding) 588 is_bytes = False 589 dir_p = opendir(opendir_path) 590 if not dir_p: 591 raise posix_error(path) 592 try: 593 result = Dirent_p() 594 while True: 595 entry = Dirent() 596 if readdir_r(dir_p, entry, result): 597 raise posix_error(path) 598 if not result: 599 break 600 name = entry.d_name 601 if name not in (b'.', b'..'): 602 if not is_bytes: 603 name = name.decode(file_system_encoding) 604 yield PosixDirEntry(path, name, entry.d_type, entry.d_ino) 605 finally: 606 if closedir(dir_p): 607 raise posix_error(path) 608 609 if _scandir is not None: 610 scandir_c = _scandir.scandir 611 DirEntry_c = _scandir.DirEntry 612 613 if _scandir is not None: 614 scandir = scandir_c 615 DirEntry = DirEntry_c 616 elif ctypes is not None and have_dirent_d_type: 617 scandir = scandir_python 618 DirEntry = PosixDirEntry 619 else: 620 scandir = scandir_generic 621 DirEntry = GenericDirEntry 622 623 624# Some other system -- no d_type or stat information 625else: 626 scandir = scandir_generic 627 DirEntry = GenericDirEntry 628 629 630def _walk(top, topdown=True, onerror=None, followlinks=False): 631 """Like Python 3.5's implementation of os.walk() -- faster than 632 the pre-Python 3.5 version as it uses scandir() internally. 633 """ 634 dirs = [] 635 nondirs = [] 636 637 # We may not have read permission for top, in which case we can't 638 # get a list of the files the directory contains. os.walk 639 # always suppressed the exception then, rather than blow up for a 640 # minor reason when (say) a thousand readable directories are still 641 # left to visit. That logic is copied here. 642 try: 643 scandir_it = scandir(top) 644 except OSError as error: 645 if onerror is not None: 646 onerror(error) 647 return 648 649 while True: 650 try: 651 try: 652 entry = next(scandir_it) 653 except StopIteration: 654 break 655 except OSError as error: 656 if onerror is not None: 657 onerror(error) 658 return 659 660 try: 661 is_dir = entry.is_dir() 662 except OSError: 663 # If is_dir() raises an OSError, consider that the entry is not 664 # a directory, same behaviour than os.path.isdir(). 665 is_dir = False 666 667 if is_dir: 668 dirs.append(entry.name) 669 else: 670 nondirs.append(entry.name) 671 672 if not topdown and is_dir: 673 # Bottom-up: recurse into sub-directory, but exclude symlinks to 674 # directories if followlinks is False 675 if followlinks: 676 walk_into = True 677 else: 678 try: 679 is_symlink = entry.is_symlink() 680 except OSError: 681 # If is_symlink() raises an OSError, consider that the 682 # entry is not a symbolic link, same behaviour than 683 # os.path.islink(). 684 is_symlink = False 685 walk_into = not is_symlink 686 687 if walk_into: 688 for entry in walk(entry.path, topdown, onerror, followlinks): 689 yield entry 690 691 # Yield before recursion if going top down 692 if topdown: 693 yield top, dirs, nondirs 694 695 # Recurse into sub-directories 696 for name in dirs: 697 new_path = join(top, name) 698 # Issue #23605: os.path.islink() is used instead of caching 699 # entry.is_symlink() result during the loop on os.scandir() because 700 # the caller can replace the directory entry during the "yield" 701 # above. 702 if followlinks or not islink(new_path): 703 for entry in walk(new_path, topdown, onerror, followlinks): 704 yield entry 705 else: 706 # Yield after recursion if going bottom up 707 yield top, dirs, nondirs 708 709 710if IS_PY3 or sys.platform != 'win32': 711 walk = _walk 712else: 713 # Fix for broken unicode handling on Windows on Python 2.x, see: 714 # https://github.com/benhoyt/scandir/issues/54 715 file_system_encoding = sys.getfilesystemencoding() 716 717 def walk(top, topdown=True, onerror=None, followlinks=False): 718 if isinstance(top, bytes): 719 top = top.decode(file_system_encoding) 720 return _walk(top, topdown, onerror, followlinks) 721