Hands-on Path.glob vs os.listdir+fnmatch.filter one-dir lab: real matches/s on *.py files, measured on Linux localhost today in this hands-on lab for SREs.
4 min read
Match files in one directory: Path.glob('*.py') vs os.listdir + fnmatch.filter. This lab reports matches/s on Linux localhost.
It is not glob-vs-rglob-vs-walk (lab 76) and not fnmatch-vs-re pattern microbenches (lab 93). Focus: Path.glob vs listdir+fnmatch on a single directory.
Related links:
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Affiliates: 0. Flat dirs only.
Verdict up front (medium 2000 entries / 400 *.py): listdir+endswith ~721109 matches/s; listdir+fnmatch ~526570; Path.glob ~294091; Path.iterdir+suffix ~61488.
| Arm | Pattern |
|---|---|
Path.glob('*.py') | pathlib |
listdir + fnmatch.filter | classic |
listdir + endswith('.py') | fixed suffix fast path |
scandir + endswith | DirEntry |
Path.iterdir + .suffix | pathlib scan |
Script: lab-evidence/113-path-glob-vs-fnmatch/results/run_lab.py.
| Arm | matches/s | entries/s |
|---|---|---|
| listdir + endswith | 721109 | 3605546 |
| scandir + endswith | 622937 | 3114687 |
| listdir + fnmatch | 526570 | 2632850 |
| Path.glob | 294091 | 1470453 |
| Path.iterdir + suffix | 61488 | 307440 |
| Size | Path.glob | listdir+fnmatch |
|---|---|---|
| small | 295655 | 485484 |
| medium | 294091 | 526570 |
| large | 287243 | 554569 |
*.py is a cheap filter. Patterns with multiple * / ? classes push more work into fnmatch — still usually dominated by directory listing syscall cost on large folders. Re-measure if your matcher is ** recursive (again: lab 76).
*.ext on one dir → listdir/scandir + endswith leads here.fnmatch or Path.glob.Path.glob stays convenient and mid-pack; fine for tooling scripts.rglob / walk), not this post.Tooling scripts that already speak pathlib, need brace-free relative patterns, or may later grow a single * directory segment often stay on Path.glob for clarity. The ~2× gap vs listdir+fnmatch on medium here rarely matters unless you scan directories in a tight loop.
endswith(".py") is not a glob. It misses patterns like test_*.py or *.pyc exclusions. Use it only for fixed-suffix filters; otherwise fnmatch.filter / Path.glob keep semantics honest. Lab 93 covers pattern-engine speed in isolation — not directory IO.
Path.iterdir() yielding Path objects paid a clear tax versus name-only listdir/scandir arms. If you only need filenames for a filter, keep strings until you must touch the filesystem again.
Path.iterdir lists when you only need names (Path object tax showed up here).rglob accidentally when one directory was intended.Evidence: summary.json, summary.txt, tree/.
For CI file collectors, scandir+endswith on a fixed suffix is a good default; graduate to fnmatch/Path.glob when patterns get expressive.
Always verify match counts against a known fixture before trusting a faster filter in production sweeps.
One Linux box. Flat synthetic directories. Pattern fixed to *.py. Not networked FS.
At 2000 entries, listdir+fnmatch ~526570 matches/s beat Path.glob ~294091; plain endswith ~721109 led for suffix-only filters. Use Path.glob for ergonomics; listdir+fnmatch/endswith when directory scans are hot.
Lab evidence
Lab 1 Oct 2026 IST. Python 3.13.5. medium 2000/400: listdir_fnmatch 526570 matches/s; Path.glob 294091; endswith 721109. One dir only — not lab 76/93. Affiliates: 0. Evidence: lab-evidence/113-path-glob-vs-fnmatch/.
Hands-on os.stat vs pathlib.Path.stat vs os.lstat: real metadata stats/s on many regular files, measured on Linux localhost in this hands-on lab for SREs.
Lab1 Oct 20264 min read
Hands-on fnmatch.filter vs re.compile name-list filtering measured on Linux localhost.
Lab1 Oct 20264 min read
Hands-on os.scandir vs listdir vs Path.iterdir lab: real entries/s for names and is_file on a synthetic tree, measured on Linux localhost (lab) for SREs.
Lab1 Oct 20264 min read