os.walk
For each folder, os.walk yields its path and two lists of plain names. Join names with the folder path yourself. With the default topdown=True you can sort or prune the dirnames list in place, and the walk follows your edit.
Demo
import os for f in ['README.md', 'src/app.py', 'src/lib/util.py', 'tests/test_app.py']: os.makedirs(os.path.dirname(f) or '.', exist_ok=True) open(f, 'w').close() tree = [] for root, dirs, files in os.walk('.'): dirs.sort() tree.append((root.replace(os.sep, '/'), dirs, sorted(files))) tree
root starts with the top you passed ('.') and grows with os.path.join, which uses \ on Windows — hence .replace(os.sep, '/') for one result on every OS. Each folder appears once, before its sub-folders (topdown=True), and only after dirs.sort() is the visiting order reproducible: the raw order comes from the file system. In the second tab, assigning to dirs[:] changes the very list os.walk is about to descend into, so node_modules or .git is never opened.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| top | str | PathLike | yes | The folder to start from. Every dirpath begins with it (as given — relative stays relative). |
| topdown | bool | no (True) | True: a folder is yielded before its sub-folders (and you can prune). False: after them — useful for deleting bottom-up. |
| onerror | callable | no (None) | Called with the OSError when a folder cannot be listed. By default errors are silently ignored — even a missing top. |
| followlinks | bool | no (False) | Descend into symlinked folders. Off by default to avoid loops. |
Return value
generator of (str, list[str], list[str]) — (dirpath, dirnames, filenames) for every folder; fwalk adds a fourth item, dirfd.
Common patterns
import os for root, dirs, files in os.walk('project'): for name in files: path = os.path.join(root, name) print(path)
import os SKIP = {'.git', 'node_modules', '__pycache__', '.venv'} for root, dirs, files in os.walk('.'): dirs[:] = [d for d in dirs if d not in SKIP]
import os for root, dirs, files in os.walk(top, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name))
import os for root, dirs, files in os.walk('/var', onerror=lambda e: print('skipped:', e.filename)): pass
Examples
Pitfalls
import os os.makedirs('t/x') os.makedirs('t/y') seen = [] for root, dirs, files in os.walk('t'): dirs = [] seen.append(root.replace(os.sep, '/')) sorted(seen)
import os os.makedirs('t/x') os.makedirs('t/y') seen = [] for root, dirs, files in os.walk('t'): dirs.clear() seen.append(root.replace(os.sep, '/')) seen
import os os.makedirs('a/b') open('a/b/x.txt', 'w').close() [f for root, dirs, files in os.walk('a') for f in files if os.path.isfile(f)]
import os os.makedirs('a/b') open('a/b/x.txt', 'w').close() [f for root, dirs, files in os.walk('a') for f in files if os.path.isfile(os.path.join(root, f))]
import os list(os.walk('srcc'))
import os def fail(e): raise e try: list(os.walk('srcc', onerror=fail)) except FileNotFoundError as e: result = type(e).__name__ result
When to use
- Visiting every folder of a tree, with control over where to descend
- Bottom-up processing (topdown=False), e.g. cleaning up
- Only matching files by pattern → glob.glob("**/*.py", recursive=True) or Path.rglob
- Deleting a tree → shutil.rmtree; copying one → shutil.copytree
- One folder only → os.scandir
Notes
FAQ
[os.path.join(root, f) for root, dirs, files in os.walk(top) for f in files] — or with pathlib, [p for p in Path(top).rglob('*') if p.is_file()].