Cleanup jobs, disk-usage reports and incremental builds need to visit every file with its metadata.
Path.walk() (3.12+) is pathlib's version of os.walk. It yields (dirpath, dirnames, filenames) for each directory, with dirpath as a Path and the other two as lists of names:
for root, dirs, files in Path("proj").walk():
dirs[:] = [d for d in dirs if d not in {".git", "node_modules"}]Editing dirs in place prunes the walk, which is much faster than filtering rglob results after the fact because pruned directories are never listed. Use top_down=False to visit children first (needed for deleting directories), and on_error= to handle unreadable folders; by default errors are ignored.
Each file's metadata comes from path.stat():
st_sizein bytesst_mtimemodification time as a POSIX timestamp; compare withtime.time()or convert withdatetime.fromtimestamp(ts, tz=timezone.utc)
A typical maintenance job, "delete logs older than 30 days", combines walk or glob, stat().st_mtime and unlink.
p.stat() follows symlinks; p.lstat() (or stat(follow_symlinks=False)) describes the link. p.owner() and p.group() give names on Unix, and p.chmod(0o644) changes permissions.
Example
import os
import time
from pathlib import Path
for f in ["logs/app.log", "logs/old/2023.log", ".git/HEAD"]:
Path(f).parent.mkdir(parents=True, exist_ok=True)
Path(f).write_text("x" * 100, encoding="utf-8")
month_ago = time.time() - 31 * 86400
os.utime("logs/old/2023.log", (month_ago, month_ago))
sizes, stale = {}, []
for root, dirs, files in Path(".").walk():
dirs[:] = [d for d in dirs if not d.startswith(".")] # prune .git
for name in files:
st = (root / name).stat()
sizes[str(root / name)] = st.st_size
if st.st_mtime < time.time() - 30 * 86400:
stale.append(root / name)
print(sorted(sizes)) # ['logs/app.log', 'logs/old/2023.log']
print(stale) # [PosixPath('logs/old/2023.log')]Remember
Path.walk()(3.12+) yields(dirpath, dirnames, filenames)like os.walk- Prune by mutating
dirnamesin place; it skips whole subtrees stat().st_sizeandst_mtimedrive size and age based jobs
Watch out for
- Using
rglob('*')and filtering out.gitafterwards on huge trees; the excluded folders are still fully scanned.