Python os.fwalk: Traverse Directories

Published on: September 5, 2026
Reading time: 4 minutes
Folders and directories representing Python os.fwalk

os.fwalk is a Python standard-library function for traversing directory trees. It resembles os.walk, but every iteration also returns a file descriptor for the current directory. That descriptor can be passed through the dir_fd parameter supported by functions such as os.stat, os.open, os.unlink, and os.rename.

For ordinary scripts, os.walk or pathlib is often simpler. The main reason to choose os.fwalk is that directory-relative operations can reduce dependence on repeatedly resolving full paths. This is valuable in administration tools, cleanup services, scanners, package builders, and programs that operate on trees that may change while they are being processed.

Basic usage

import os

for root, dirs, files, root_fd in os.fwalk("data"):
    print(root, root_fd)
    for name in files:
        print("file:", name)

Each iteration produces four values: the current path, a mutable list of child directories, a list of file names, and the current directory descriptor. The names in files can be resolved relative to root_fd.

Why directory descriptors matter

A common pattern builds a full path, checks it, and later performs another operation on that path. Between those two actions, another process can modify or replace part of the path. This is a time-of-check to time-of-use race. Descriptor-relative operations do not solve every security problem, but they narrow the amount of path resolution that occurs after the directory has already been opened.

import os

for root, _, files, root_fd in os.fwalk("temporary"):
    for name in files:
        info = os.stat(name, dir_fd=root_fd, follow_symlinks=False)
        if info.st_size == 0:
            os.unlink(name, dir_fd=root_fd)

The example removes empty files without rebuilding the complete path for each metadata and delete operation.

The descriptor is temporary

The descriptor returned by os.fwalk remains valid only until the next iteration step. Do not save it and use it later. When a longer lifetime is necessary, duplicate it with os.dup and close the copy explicitly.

import os

for root, _, _, root_fd in os.fwalk("data"):
    saved_fd = os.dup(root_fd)
    try:
        print(os.listdir(saved_fd))
    finally:
        os.close(saved_fd)

Descriptor leaks eventually exhaust the process limit, so a try/finally block is essential.

Top-down traversal

The default is topdown=True. A parent directory is yielded before its children, and the dirs list may be modified in place to prune the traversal.

import os

for root, dirs, files, root_fd in os.fwalk("project", topdown=True):
    dirs[:] = [d for d in dirs if d not in {".git", ".venv", "__pycache__"}]
    print(root, len(files))

Pruning saves system calls and prevents the program from entering irrelevant or expensive trees.

Bottom-up traversal

With topdown=False, children are yielded before their parent. This is useful when deleting a tree because files and nested directories must be removed first.

import os

base = "old-output"
for root, dirs, files, root_fd in os.fwalk(base, topdown=False):
    for name in files:
        os.unlink(name, dir_fd=root_fd)
    for name in dirs:
        os.rmdir(name, dir_fd=root_fd)
os.rmdir(base)

Destructive code must validate the base path, handle permissions and partial failures, and avoid accepting an untrusted path directly. A dry-run mode and audit log are strongly recommended.

follow_symlinks defaults to False. This safer default avoids accidentally leaving the intended tree and reduces the risk of cycles. When links must be followed, track visited directories by device and inode so the traversal cannot loop indefinitely.

Metadata calls should also state their link policy explicitly. Passing follow_symlinks=False to os.stat inspects the link itself instead of its target.

Error handling

Files may disappear, permissions may change, and remote storage can fail while a tree is being walked. Use the onerror callback for directory-reading errors and catch specific exceptions around individual entries.

import os

def report(error):
    print(f"Could not access {error.filename}: {error}")

for root, _, files, root_fd in os.fwalk("data", onerror=report):
    for name in files:
        try:
            info = os.stat(name, dir_fd=root_fd, follow_symlinks=False)
        except FileNotFoundError:
            continue
        except PermissionError as error:
            report(error)
        else:
            print(root, name, info.st_size)

A disappearing file is often a normal event in a live directory, not necessarily a fatal condition.

Comparison with os.walk and pathlib

pathlib.Path.rglob offers a readable object-oriented API for common automation. os.walk is familiar and portable. os.fwalk is most appropriate when descriptor-relative system calls are central to the design. Using it without that need adds complexity.

Portability also matters. Not every platform supports every dir_fd argument. Check os.supports_dir_fd and test on all target operating systems.

import os

if os.stat in os.supports_dir_fd:
    print("os.stat supports dir_fd")

Building a file inventory

import os
from dataclasses import dataclass

@dataclass
class Item:
    path: str
    size: int
    mode: int

def inventory(base: str) -> list[Item]:
    result = []
    for root, dirs, files, root_fd in os.fwalk(base, follow_symlinks=False):
        dirs[:] = [d for d in dirs if d != ".git"]
        for name in files:
            try:
                info = os.stat(name, dir_fd=root_fd, follow_symlinks=False)
            except (FileNotFoundError, PermissionError):
                continue
            result.append(Item(os.path.join(root, name), info.st_size, info.st_mode))
    return result

For very large trees, turn this function into a generator instead of storing every result. Stream items to a database, queue, or report writer.

Renaming relative to descriptors

Descriptor-relative operations are also useful when moving entries inside known directories.

import os

for root, _, files, root_fd in os.fwalk("incoming"):
    for name in files:
        if name.endswith(".part"):
            final_name = name.removesuffix(".part")
            os.rename(name, final_name, src_dir_fd=root_fd, dst_dir_fd=root_fd)

The operation keeps both names relative to the same opened directory. Production code should prevent collisions and define how interrupted transfers are recovered.

Performance considerations

os.fwalk does not remove file-system latency. Calls to stat still reach the operating system. Prune early, avoid requesting metadata you do not need, and be cautious with network file systems. The number of system calls usually matters more than small Python-level optimizations.

Unlimited parallelism can overload disks or remote servers. Bound the number of workers and make processing idempotent so failed batches can be retried safely.

Testing strategies

Tests should create temporary trees containing regular files, nested directories, empty files, unreadable entries when supported, and symbolic links. Verify top-down pruning, bottom-up cleanup, error handling, and descriptor closure. Never run destructive tests against a developer’s real directory.

Best practices

Validate the root, keep symbolic-link following disabled by default, duplicate descriptors only when required, close every duplicate, treat transient disappearance as expected, and log destructive actions. Prefer a simpler API when descriptor-relative operations provide no clear benefit.

Related Academify guides include Python pathlib, the os module, shutil, and contextlib. External references are the official os.fwalk documentation and the Python files and directories documentation.

Conclusion

os.fwalk combines recursive traversal with a descriptor for each current directory. It enables precise relative operations, reduces some path-related races, and gives system tools better control over mutable directory trees. It is not a universal replacement for os.walk, but it is a valuable option when safer descriptor-based file operations are required.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Software developer reviewing Python code and overridden methods
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    typing.override: Validate Method Overrides

    Learn Python typing.override for safer method overrides, compatible signatures, inheritance contracts, and reliable refactoring.

    Ler mais

    Tempo de leitura: 6 minutos
    05/09/2026
    Software developer working with Python queues and threads
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    queue.SimpleQueue: Thread-Safe FIFO Queue

    Learn Python queue.SimpleQueue for thread-safe FIFO queues, worker pipelines, clean shutdown, and reliable producer-consumer designs.

    Ler mais

    Tempo de leitura: 5 minutos
    04/09/2026
    Software developer working with Python enums and API code
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python StrEnum: String Enums

    Learn Python StrEnum for string-based enums, input validation, JSON serialization, APIs, configuration, and cleaner domain contracts.

    Ler mais

    Tempo de leitura: 4 minutos
    04/09/2026
    Folders and directories for Python contextlib.chdir
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    contextlib.chdir: Restore Directories Automatically

    Learn Python contextlib.chdir to change directories temporarily, restore paths safely, isolate tests, and avoid global-state bugs.

    Ler mais

    Tempo de leitura: 5 minutos
    03/09/2026
    Python code execution and performance monitoring
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    sys.monitoring: Low-Overhead Instrumentation

    Learn Python sys.monitoring for low-overhead instrumentation with selective events, callbacks, tooling, and safe observability.

    Ler mais

    Tempo de leitura: 5 minutos
    03/09/2026
    Software developer organizing object data with Python operator.attrgetter
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    operator.attrgetter: Sort Objects by Attributes

    Learn Python operator.attrgetter to sort, group, and transform objects by simple or nested attributes with clearer reusable code.

    Ler mais

    Tempo de leitura: 4 minutos
    02/09/2026