Collecting inputs like 'every .py file in the project' is a one-liner with glob.
p.glob(pattern) yields paths below p that match a shell-style pattern:
*matches any characters within one path component?matches one character;[abc]a character set**matches any number of directories, including none
So p.glob("*.csv") searches one level, p.glob("**/*.csv") the whole tree, and p.rglob("*.csv") is shorthand for the latter. Results come in arbitrary order; sort them for stable output.
Details worth knowing:
- Hidden files (starting with
.) are matched by*, unlike in most shells. - Matching follows the platform's case rules by default; pass
case_sensitive=True/False(3.12+) to override. - In 3.13, a pattern ending in
**yields both files and directories; earlier versions yielded only directories. Add/*or a suffix pattern if you want files only. globreturns a generator; wrap inlist()orsorted()to reuse the results.- Symlinked directories are not followed by
**unless you passrecurse_symlinks=True(3.13).
To exclude directories such as .git or node_modules, filter the results (".git" not in p.parts), or use Path.walk() where you can prune whole subtrees for speed.
Example
from pathlib import Path
for f in ["src/app.py", "src/util/io.py", "src/util/README.md",
"tests/test_app.py", ".venv/lib/x.py"]:
p = Path(f)
p.parent.mkdir(parents=True, exist_ok=True)
p.touch()
root = Path(".")
print(sorted(map(str, root.glob("src/*.py")))) # ['src/app.py']
py = sorted(str(p) for p in root.rglob("*.py") if ".venv" not in p.parts)
print(py) # ['src/app.py', 'src/util/io.py', 'tests/test_app.py']
print(sorted(p.name for p in root.glob("**/test_*.py"))) # ['test_app.py']
print(len(list(root.glob("src/util/*.MD", case_sensitive=False)))) # 1Remember
glob('*.ext')searches one level;rglob('*.ext')orglob('**/*.ext')the whole tree- Results are unordered generators; sort and list them as needed
- Filter by
p.partsor usePath.walkto skip folders like .venv
Watch out for
- Expecting
glob('*.py')to search subdirectories; it only looks one level deep.