pathlib vs os: performance trade-offs

pathlib · Expert · 9 min

Scanning millions of files with naive pathlib code can be several times slower than necessary.

Path objects are convenient, but each one is a Python object that parses and stores its parts, and many operations create new objects. In typical scripts this cost is irrelevant compared to disk I/O. It starts to matter when you touch hundreds of thousands of paths in a loop.

Where the time goes:

  • Repeated stat calls. p.is_file(), p.is_dir() and p.stat() each issue a system call. Calling all three per file triples the work. os.scandir returns DirEntry objects whose type is usually known from the directory listing itself, with no extra call; os.walk and Path.walk build on it.
  • Object creation. root / name for every entry allocates a Path and parses it; string joins are cheaper.
  • rglob over huge trees you'd prune anyway. Prefer Path.walk and prune.

Recent releases (3.12 and 3.13) reworked glob internally for speed, so upgrading Python can itself help glob-heavy code.

Practical approach: write clear pathlib code first; profile; then, in the hot loop only, drop to os.scandir and strings, converting results to Path at the end. Caching stat results when you need several fields from the same file also helps.

Example

import os
import timeit
from pathlib import Path

d = Path("many")
d.mkdir()
for i in range(2000):
    (d / f"f{i}.txt").touch()

def with_pathlib():
    return sum(p.stat().st_size for p in d.iterdir() if p.is_file())

def with_scandir():
    with os.scandir(d) as it:
        return sum(e.stat().st_size for e in it if e.is_file())

print(with_pathlib() == with_scandir())   # True
t1 = timeit.timeit(with_pathlib, number=5)
t2 = timeit.timeit(with_scandir, number=5)
print(t2 < t1)                            # True

Remember

  • File system calls dominate: avoid redundant stat/is_file calls per entry
  • os.scandir reuses type info from the listing; walk APIs build on it
  • Write pathlib first, profile, and drop to os/scandir only in hot loops

Watch out for

  • Calling exists(), is_file() and stat() on the same path in sequence inside a large loop.

Check yourself

  1. Why is os.scandir with entry.is_file() typically faster than Path.iterdir with p.is_file()?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/pathlib.html

Builds on

Unlocks

Nothing builds on this yet.