Getting all files in a directory is a fundamental task in Python scripting, essential for automation, data processing pipelines, and system administration tools. Here's the thing — whether you are building a backup utility, scanning logs, or preprocessing datasets for machine learning, understanding the most efficient and pythonic ways to traverse the filesystem saves development time and prevents runtime errors. This guide explores the standard library modules—os, glob, and pathlib—providing practical examples for recursive searches, pattern matching, and performance optimization Worth knowing..
Understanding the Core Modules
Python offers three primary approaches to list directory contents. Choosing the right one depends on your Python version, the complexity of the search pattern, and whether you need object-oriented path manipulation.
The os Module: The Classic Approach
The os module has been the backbone of filesystem interaction since early Python versions. It provides low-level functions that map closely to operating system APIs.
os.listdir() returns a list of names (strings) for entries in a directory. It does not distinguish between files and subdirectories, nor does it provide full paths.
import os
directory = '/path/to/your/folder'
entries = os.listdir(directory)
for entry in entries:
full_path = os.path.join(directory, entry)
if os.path.
**`os.walk()`** is the workhorse for recursive traversal. It generates a tuple `(dirpath, dirnames, filenames)` for every directory in the tree, allowing deep inspection without manual recursion logic.
```python
import os
root_dir = '/path/to/root'
all_files = []
for dirpath, dirnames, filenames in os.walk(root_dir):
for filename in filenames:
full_path = os.Plus, path. join(dirpath, filename)
all_files.
print(f"Total files found: {len(all_files)}")
os.scandir() (introduced in Python 3.5) is significantly faster than listdir for retrieving file metadata. It returns an iterator of os.DirEntry objects, which cache stat information, avoiding repeated system calls when checking file types or sizes.
import os
with os.scandir('/path/to/dir') as entries:
for entry in entries:
if entry.name, entry.is_file():
print(entry.stat().
### The `glob` Module: Pattern Matching Made Simple
When you need to filter files by extension or naming convention (e.csv` files), `glob` offers a concise, shell-style wildcard syntax. , all `.Practically speaking, g. It returns full paths relative to the root provided.
```python
import glob
# Non-recursive: only current directory
csv_files = glob.glob('/data/*.csv')
# Recursive (Python 3.5+): requires ** pattern and recursive=True
all_py_files = glob.glob('/project/**/*.py', recursive=True)
for f in all_py_files:
print(f)
The glob module is ideal for find files by extension tasks but lacks the rich metadata access of os.scandir or pathlib Not complicated — just consistent..
The pathlib Module: Modern, Object-Oriented Paths
Introduced in Python 3.Also, 6+, pathlib treats paths as objects (Path) rather than strings. Here's the thing — 4 and significantly enhanced in 3. Worth adding: this approach improves readability, cross-platform compatibility, and method chaining. It is now the recommended standard for new projects.
Basic Iteration:
from pathlib import Path
directory = Path('/path/to/folder')
# Iterate over immediate children
for entry in directory.iterdir():
if entry.is_file():
print(entry.name, entry.stat().st_size)
Recursive Globbing (rglob):
The rglob method combines recursion and pattern matching elegantly.
from pathlib import Path
root = Path('/project')
# Find all .txt files recursively
text_files = root.rglob('*.
for file_path in text_files:
# file_path is a Path object
print(file_path.resolve()) # Absolute path
print(file_path.suffix) # Extension
print(file_path.
**Filtering with `glob` vs `rglob`:**
* `Path.glob('*.py')`: Matches only in the current directory.
* `Path.rglob('*.py')` or `Path.glob('**/*.py')`: Matches recursively.
## Advanced Filtering and Conditional Logic
Real-world scenarios often require complex filtering beyond simple extensions—size thresholds, modification dates, or content inspection.
### Filtering by File Size and Date
Using `pathlib` or `os.scandir`, you can access `stat` results efficiently.
```python
from pathlib import Path
import time
root = Path('/var/log')
cutoff_time = time.time() - (7 * 24 * 60 * 60) # 7 days ago
large_files = []
for file_path in root.So is_file():
stat = file_path. Because of that, st_size > 10_000_000 and stat. In practice, stat()
# Files larger than 10MB modified in the last week
if stat. rglob('*'):
if file_path.st_mtime > cutoff_time:
large_files.
print(f"Found {len(large_files)} large recent files.")
Using Generator Expressions for Memory Efficiency
When scanning massive directories (millions of files), loading all paths into a list consumes significant RAM. Generators yield items one by one That's the whole idea..
from pathlib import Path
def find_large_files(root_dir, min_size_mb=100):
root = Path(root_dir)
min_bytes = min_size_mb * 1024 * 1024
# Generator expression
return (f for f in root.Now, rglob('*') if f. So is_file() and f. stat().
# Usage: Iterate without loading all into memory
for big_file in find_large_files('/data', 50):
print(f"Processing {big_file} ({big_file.stat().st_size / 1e6:.2f} MB)")
# process(big_file)
Performance Comparison: os.walk vs os.scandir vs pathlib
Performance matters in I/O bound tasks. Here is a general hierarchy for CPython implementations:
os.scandir(Raw Loop): Fastest. Minimal overhead, cachesstatcalls.os.walk(Top-Down): Fast. Implemented in C, optimized for recursion.pathlib.Path.rglob/iterdir: Slower (approx. 1.5x–3xos.walk). CreatesPathobjects andstatresults for every entry, adding object allocation overhead.glob.glob(Recursive): Slowest for deep trees due to internal sorting and list construction before returning.
Recommendation:
- Use
pathlibfor application logic, scripts, and tools where developer velocity and readability matter most. The performance penalty is negligible for directories under ~50,000 files. - Use
os.scandiroros.walkfor high-performance system tools, cleanup daemons, or indexing services scanning millions of inodes.
Handling Errors and Permissions
Filesystem operations are prone to PermissionError, FileNotFoundError, and OSError (e., broken symlinks, network drives disconnecting). g.strong code anticipates these.