Path.glob

glob('*.py') looks in this folder only; glob('**/*.py') and rglob('*.py') look everywhere below it. Unlike the glob module, pathlib's * also matches dotfiles and ** is always recursive. Results come in file-system order — sort them.

pathlib methodPython 3.4+ (walk 3.12+)Live demo
Common call
sorted(Path('src').rglob('*.py'))
Returns
Path objects under the folder
Replaces
glob.glob(…, recursive=True) and os.walk
Watch out
unordered; * matches dotfiles; a pattern ending in '/' returns folders only
Path.glob(patternpattern — Relative glob: * ? [seq] within a component, ** for any depth. An absolute or empty pattern is an error.type: str | PurePath · required, *, case_sensitivecase_sensitive — 3.12+. None = the platform rule: case-sensitive on POSIX, not on Windows.type: bool | None · default: None=None, recurse_symlinksrecurse_symlinks — 3.13+. Follow symlinks to folders when expanding **.type: bool · default: False=False) / .rglob(pattern, …) / .walk(top_down=True, on_erroron_error — walk only: called with the OSError when a folder cannot be listed; errors are ignored by default.type: callable | None · default: None=None, follow_symlinksfollow_symlinks — walk only: descend into symlinked folders.type: bool · default: False=False)
→ Iterator[Path] | Iterator[tuple[Path, list[str], list[str]]]

Demo

Live evaluation
Create a small tree, then glob it from '.'.
Try:
Inputs
fileslist[str]files to create, comma-separated
patternstrglob pattern
Code
from pathlib import Path
for f in ['app.py', '.hidden.py', 'src/core.py', 'src/lib/util.py', 'docs/guide.md']:
    Path(f).parent.mkdir(parents=True, exist_ok=True)
    Path(f).touch()
sorted(p.as_posix() for p in Path('.').glob('*.py'))
Result
['.hidden.py', 'app.py']

'*.py' stays in the top folder but still finds '.hidden.py' — pathlib globbing treats dotfiles like any other name. '**' matches zero or more folders, so '**/*.py' finds app.py too. A trailing slash selects folders only ('**/' lists '.' itself and every sub-folder). walk() reports the starting folder as '.', and each triple lists names, not paths.

Parameters

NameTypeRequiredDescription
patternstr | PurePathyesRelative glob: * ? [seq] within a component, ** for any depth. An absolute or empty pattern is an error.
case_sensitivebool | Noneno (None)3.12+. None = the platform rule: case-sensitive on POSIX, not on Windows.
recurse_symlinksboolno (False)3.13+. Follow symlinks to folders when expanding **.
top_downboolno (True)walk only: yield a folder before its sub-folders; with top_down=True you may prune dirnames in place.
on_errorcallable | Noneno (None)walk only: called with the OSError when a folder cannot be listed; errors are ignored by default.
follow_symlinksboolno (False)walk only: descend into symlinked folders.

Return value

Iterator[Path] | Iterator[tuple[Path, list[str], list[str]]] — glob / rglob yield matching paths; walk yields one triple per folder.

Common patterns

All files of a type, recursively
Sort for reproducible output.
from pathlib import Path
for path in sorted(Path('data').rglob('*.csv')):
    process(path)
Several extensions
glob has no {a,b} alternation; filter on suffix instead.
from pathlib import Path
images = [p for p in Path('img').rglob('*') if p.suffix.lower() in {'.jpg', '.jpeg', '.png'}]
Prune folders while walking
With top_down=True, editing dirnames in place skips those sub-trees.
from pathlib import Path
for folder, dirs, files in Path('.').walk():
    dirs[:] = [d for d in dirs if d not in {'.git', 'node_modules', '__pycache__'}]
    for name in files:
        print(folder / name)
Total size of a tree
rglob plus stat; skip folders.
from pathlib import Path
total = sum(p.stat().st_size for p in Path('build').rglob('*') if p.is_file())

Examples

1. One folder, by extension
from pathlib import Path for n in ['a.py', 'b.py', 'c.txt']: Path(n).touch() sorted(p.name for p in Path('.').glob('*.py'))
Returns
['a.py', 'b.py']
2. Recursive with **
from pathlib import Path Path('pkg/sub').mkdir(parents=True) for n in ['top.py', 'pkg/a.py', 'pkg/sub/b.py']: Path(n).touch() sorted(p.as_posix() for p in Path('.').glob('**/*.py'))
Returns
['pkg/a.py', 'pkg/sub/b.py', 'top.py']
3. rglob is glob("**/…")
from pathlib import Path Path('pkg/sub').mkdir(parents=True) for n in ['top.py', 'pkg/a.py', 'pkg/sub/b.py']: Path(n).touch() sorted(Path('.').rglob('*.py')) == sorted(Path('.').glob('**/*.py'))
Returns
True
4. Dotfiles are matched
from pathlib import Path for n in ['.env', 'app.cfg']: Path(n).touch() sorted(p.name for p in Path('.').glob('*'))
Returns
['.env', 'app.cfg']
5. Folders only (trailing slash)
from pathlib import Path Path('src').mkdir() Path('README.md').touch() [p.name for p in Path('.').glob('*/')]
Returns
['src']
6. walk yields names per folder
from pathlib import Path Path('docs').mkdir() Path('docs/a.md').touch() Path('main.py').touch() sorted((d.as_posix(), dirs, files) for d, dirs, files in Path('.').walk())
Returns
[('.', ['docs'], ['main.py']), ('docs', [], ['a.md'])]
7. No matches is not an error
from pathlib import Path list(Path('.').glob('*.nothing'))
Returns
[]
8. Absolute patterns are refused
from pathlib import Path list(Path('.').glob('/etc/*'))
Returns
NotImplementedError: Non-relative patterns are unsupported

Pitfalls

1. Using a glob result without sorting
glob yields in directory order, which differs between file systems. Sort before displaying, diffing or processing in order.
generator
from pathlib import Path
type(Path('.').glob('*')).__name__
'map'
sorted()
from pathlib import Path
for n in ['b.txt', 'a.txt']:
    Path(n).touch()
[p.name for p in sorted(Path('.').glob('*.txt'))]
['a.txt', 'b.txt']
2. Expecting brace alternation
'*.{jpg,png}' is not special in pathlib: the braces are literal characters, so nothing matches.
{jpg,png}
from pathlib import Path
for n in ['a.jpg', 'b.png']:
    Path(n).touch()
list(Path('.').glob('*.{jpg,png}'))
[]
filter suffix
from pathlib import Path
for n in ['a.jpg', 'b.png']:
    Path(n).touch()
sorted(p.name for p in Path('.').iterdir() if p.suffix in {'.jpg', '.png'})
['a.jpg', 'b.png']
3. Consuming the iterator twice
glob returns a one-shot iterator; the second pass is empty. Store list(...) if you need it again.
reuse
from pathlib import Path
Path('a.txt').touch()
found = Path('.').glob('*.txt')
first = list(found)
list(found)
[]
list() once
from pathlib import Path
Path('a.txt').touch()
found = list(Path('.').glob('*.txt'))
len(found), len(found)
(1, 1)

When to use

Use it
  • Finding files by name or extension (glob / rglob)
  • Walking a tree folder by folder, with pruning (walk, 3.12+)
Reach for something else
  • Matching paths you already have → PurePath.full_match
  • Hidden-file-aware shell semantics → the glob module (glob.glob skips dotfiles by default)

Notes

CPython impl
Lib/pathlib/_local.py: glob compiles the pattern into selectors from glob._StringGlobber over os.scandir; rglob prefixes "**"; walk wraps os.walk
vs glob module
Dotfiles are not special (like include_hidden=True), ** is always recursive (like recursive=True), and ** does not follow symlinks unless recurse_symlinks=True
Changed in 3.13
A pattern ending in ** returns files and folders (folders only before); OSErrors while scanning are suppressed; pattern may be path-like
Changed in 3.11
A pattern ending with a separator returns only folders
Case
Windows: case-insensitive by default; POSIX: case-sensitive — pass case_sensitive= to force

FAQ

sorted(Path('folder').rglob('*.ext')) — or glob('**/*.ext'). Both search every sub-folder.