Efficient Directory Scanning with os.scandir()
Scan directory entries efficiently with os.scandir() and DirEntry, filter types safely, manage the iterator, and understand metadata caching and errors.
Intermediate
os.scandir() is useful when you need to inspect the entries in one directory and often need their type or metadata as well. It yields os.DirEntry objects instead of bare names. This lesson focuses on that scanning interface—not on building paths (Paths and pathlib), querying metadata in general (Reading Filesystem Metadata), matching patterns (glob and fnmatch), or walking an entire tree.
Open a directory scan and close it reliably
Use os.scandir(path) as a context manager. The iterator is closed when the with block ends, including when the block exits early or an exception is raised. Entries arrive in arbitrary order, so sort their names when stable output matters. Do not assume a scan is a snapshot: filesystem changes during iteration have unspecified inclusion behavior.
Output
Ready to run.
Use DirEntry for entry names and types
Each DirEntry has a name and path, plus methods such as is_file() and is_dir(). These methods may use type information returned by the operating system while scanning, so they can avoid extra system calls in common cases. They may still perform system calls and raise OSError. Unlike recursive globbing, scanning one directory is non-recursive: child directories are entries, not places the iterator descends into.
Output
Ready to run.
Choose whether type checks follow symbolic links
is_file() and is_dir() follow symbolic links by default. Pass follow_symlinks=False when you need to classify the directory entry itself rather than its target. Symbolic-link creation is not available or permitted on every system, so portable code must handle the relevant OSError or NotImplementedError instead of assuming the operation will succeed.
Output
Ready to run.
Read entry metadata only when needed
DirEntry.stat() returns a stat_result, with separate cached results for follow_symlinks=True and False. Its first-call system-call behavior differs by platform: on Unix it always requires a system call, while Windows can reuse scan data for some cases. Avoid assuming a fixed number of system calls or a performance gain for every directory. Use the size and type helpers only when your task needs them.
Output
Ready to run.
Refresh metadata when an entry may have changed
DirEntry objects are intended for short-lived scanning, not as a durable metadata cache. Once a DirEntry.stat() result has been cached, asking that same entry again may return the cached result even if the file has changed. When freshness matters, make a new os.stat(entry.path) query and handle its possible OSError. This still does not make a later open or update atomic with the metadata check.
Output
Ready to run.
Handle scan and entry errors explicitly
Opening a scan can fail before iteration begins—for example, if the directory does not exist or cannot be accessed. Entry methods can fail later if permissions or filesystem state change. Catch the specific errors your program can handle; a single try block around os.scandir() does not eliminate every error that might occur while processing entries.
Output
Ready to run.
Use DirEntry with higher-level path tools
DirEntry.name is the entry’s base name; DirEntry.path is the path formed from the argument passed to scandir() and that name. When the scan argument is relative, the entry path remains relative, so changing the working directory can change what it resolves to. DirEntry implements the path-like protocol. Convert its path to a pathlib.Path when you want to continue with object-oriented path operations, and sort explicitly when output order matters.
Output
Ready to run.
Choose the right directory API
Use pathlib.Path.iterdir() for straightforward direct-child iteration that should produce Path objects. Use glob or Path.glob() when the goal is matching names with patterns. Use os.scandir() when one-pass directory processing needs DirEntry type information or metadata and you can benefit from those attributes while scanning. For recursive traversal, choose a deliberate tree-walking API instead of assuming scandir() descends automatically. In all cases, handle errors according to the job and do not retain DirEntry objects as long-lived metadata records.
Close the iterator reliably
Complete the context-manager expression that opens a scanner and binds its entries.
import os
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as temporary:
root = Path(temporary)
(root / "a.txt").touch()
with os.scandir(root) as :
names = sorted(entry.name for entry in entries)
assert names == ["a.txt"]
print(names)
Filter only non-symlink regular files
Choose the DirEntry call that tests for a regular file without following a symbolic link.
import os
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as temporary:
root = Path(temporary)
(root / "report.txt").touch()
with os.scandir(root) as entries:
names = sorted(entry.name for entry in entries if )
assert names == ["report.txt"]
print(names)
Make the result deterministic
Complete the expression that sorts entry names before printing them.
import os
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as temporary:
root = Path(temporary)
(root / "b.txt").touch()
(root / "a.txt").touch()
with os.scandir(root) as entries:
names = (entry.name for entry in entries)
assert names == ["a.txt", "b.txt"]
print(names)
Request fresh metadata
Choose the expression that queries fresh status information rather than reusing the DirEntry cache.
import os
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as temporary:
root = Path(temporary)
note = root / "note.txt"
note.write_bytes(b"old")
with os.scandir(root) as entries:
entry = next(entries)
cached_size = entry.stat().st_size
note.write_bytes(b"new content")
size =
assert cached_size == 3 and size == 11
print(cached_size, size)
Handle a missing scan directory
Complete the exception type for an expected missing-directory error.
import os
try:
os.scandir("not-a-real-directory")
except :
print("missing")
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.