scandir.py 24 KB

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